use std::env;
use std::env::VarError;

use thiserror::Error;
use uuid::Uuid;

#[derive(Debug, Error)]
pub enum EnvValidationError {
    #[error("{name} is required but was not set. {help}")]
    MissingVar {
        name: &'static str,
        help: &'static str,
    },
    #[error("{name} is set but empty. {help}")]
    EmptyVar {
        name: &'static str,
        help: &'static str,
    },
    #[error("{name} is invalid: {reason}")]
    InvalidVar { name: &'static str, reason: String },
}

const SUNO_BASE_URL_HELP: &str = "Set SUNO_BASE_URL in the repository .env (for example SUNO_BASE_URL=http://127.0.0.1:8000) or export it before running sunocore tests.";
const SUNO_API_KEY_HELP: &str = "Set SUNO_API_KEY in the repository .env or export it before running sunocore tests if your backend requires authentication.";
const WORKSPACE_ID_HELP: &str = "Set WORKSPACE_ID to a valid UUID in the repository .env or export it before running sunocore tests.";
const CDN_BASE_URL_HELP: &str =
    "Override CDN_BASE_URL in the repository .env if you need a custom CDN host.";

pub const DEFAULT_WORKSPACE_ID: &str = "ba1f235a-8739-439d-b298-a61e0fc3476a";
pub const DEFAULT_CDN_BASE_URL: &str = "https://cdn1.suno.ai";

pub fn suno_base_url() -> Result<String, EnvValidationError> {
    require_env_var("SUNO_BASE_URL", SUNO_BASE_URL_HELP)
}

pub fn suno_api_key() -> Result<Option<String>, EnvValidationError> {
    optional_env_var("SUNO_API_KEY", SUNO_API_KEY_HELP)
}

pub fn workspace_id() -> Result<Uuid, EnvValidationError> {
    match env::var("WORKSPACE_ID") {
        Ok(value) => {
            let trimmed = value.trim();
            if trimmed.is_empty() {
                return Err(EnvValidationError::EmptyVar {
                    name: "WORKSPACE_ID",
                    help: WORKSPACE_ID_HELP,
                });
            }
            parse_workspace_id(trimmed)
        }
        Err(VarError::NotPresent) => parse_workspace_id(DEFAULT_WORKSPACE_ID),
        Err(VarError::NotUnicode(_)) => Err(EnvValidationError::InvalidVar {
            name: "WORKSPACE_ID",
            reason: "value contains invalid UTF-8 bytes".into(),
        }),
    }
}

pub fn cdn_base_url() -> Result<String, EnvValidationError> {
    match optional_env_var("CDN_BASE_URL", CDN_BASE_URL_HELP)? {
        Some(value) => Ok(value),
        None => Ok(DEFAULT_CDN_BASE_URL.to_string()),
    }
}

// Unwrap versions that panic with helpful error messages for use in tests
pub fn suno_base_url_unwrap() -> String {
    suno_base_url().unwrap_or_else(|err| panic!("{err}"))
}

pub fn suno_api_key_unwrap() -> Option<String> {
    suno_api_key().unwrap_or_else(|err| panic!("{err}"))
}

pub fn workspace_id_unwrap() -> Uuid {
    workspace_id().unwrap_or_else(|err| panic!("{err}"))
}

pub fn cdn_base_url_unwrap() -> String {
    cdn_base_url().unwrap_or_else(|err| panic!("{err}"))
}

fn require_env_var(name: &'static str, help: &'static str) -> Result<String, EnvValidationError> {
    match env::var(name) {
        Ok(value) => {
            let trimmed = value.trim();
            if trimmed.is_empty() {
                Err(EnvValidationError::EmptyVar { name, help })
            } else {
                Ok(trimmed.to_owned())
            }
        }
        Err(VarError::NotPresent) => Err(EnvValidationError::MissingVar { name, help }),
        Err(VarError::NotUnicode(_)) => Err(EnvValidationError::InvalidVar {
            name,
            reason: "value contains invalid UTF-8 bytes".into(),
        }),
    }
}

fn optional_env_var(
    name: &'static str,
    help: &'static str,
) -> Result<Option<String>, EnvValidationError> {
    match env::var(name) {
        Ok(value) => {
            let trimmed = value.trim();
            if trimmed.is_empty() {
                Err(EnvValidationError::EmptyVar { name, help })
            } else {
                Ok(Some(trimmed.to_owned()))
            }
        }
        Err(VarError::NotPresent) => Ok(None),
        Err(VarError::NotUnicode(_)) => Err(EnvValidationError::InvalidVar {
            name,
            reason: "value contains invalid UTF-8 bytes".into(),
        }),
    }
}

fn parse_workspace_id(value: &str) -> Result<Uuid, EnvValidationError> {
    Uuid::parse_str(value).map_err(|err| EnvValidationError::InvalidVar {
        name: "WORKSPACE_ID",
        reason: format!(
            "expected a valid UUID (example: {}). parser error: {}",
            DEFAULT_WORKSPACE_ID, err
        ),
    })
}
