use anyhow::{Context, Result};
use comfy_table::{modifiers::UTF8_ROUND_CORNERS, presets::UTF8_FULL, Attribute, Cell, Table};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

#[derive(Debug, Deserialize)]
struct EnvVarResponse {
    key: String,
    value: String, // Will be masked ("••••••••") for protected secrets
    is_secret: bool,
    is_protected: bool,
}

#[derive(Debug, Deserialize)]
struct EnvVarsResponse {
    env_vars: Vec<EnvVarResponse>,
}

#[derive(Debug, Serialize)]
struct SetEnvVarRequest {
    value: String,
    #[serde(default)]
    is_secret: bool,
    #[serde(default)]
    is_protected: bool,
}

/// Fetch environment variables from a project (internal helper)
async fn fetch_env_vars_response(
    http_client: &Client,
    backend_url: &str,
    token: &str,
    project: &str,
) -> Result<EnvVarsResponse> {
    let url = format!("{}/api/v1/projects/{}/env", backend_url, project);

    let response = http_client
        .get(&url)
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .context("Failed to fetch environment variables")?;

    if !response.status().is_success() {
        let status = response.status();
        let error_text = response
            .text()
            .await
            .unwrap_or_else(|_| "Unknown error".to_string());
        anyhow::bail!(
            "Failed to fetch environment variables (status {}): {}",
            status,
            error_text
        );
    }

    let env_vars_response: EnvVarsResponse = response
        .json()
        .await
        .context("Failed to parse environment variables response")?;

    Ok(env_vars_response)
}

/// Fetch preview environment variables — the full set a deployment would receive.
///
/// Returns:
/// - Loadable vars (non-secret + unprotected secrets, with decrypted values)
/// - Protected keys (value masked, cannot be loaded locally)
pub async fn fetch_preview_env_vars(
    http_client: &Client,
    backend_url: &str,
    token: &str,
    project: &str,
    deployment_group: &str,
) -> Result<(Vec<(String, String)>, Vec<String>)> {
    let url = format!(
        "{}/api/v1/projects/{}/env/preview?deployment_group={}",
        backend_url, project, deployment_group
    );

    let response = http_client
        .get(&url)
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .context("Failed to fetch preview environment variables")?;

    if !response.status().is_success() {
        let status = response.status();
        let error_text = response
            .text()
            .await
            .unwrap_or_else(|_| "Unknown error".to_string());
        anyhow::bail!(
            "Failed to fetch preview environment variables (status {}): {}",
            status,
            error_text
        );
    }

    let env_response: EnvVarsResponse = response
        .json()
        .await
        .context("Failed to parse preview environment variables response")?;

    let mut loadable_vars = Vec::new();
    let mut protected_keys = Vec::new();

    for var in env_response.env_vars {
        if var.is_protected {
            protected_keys.push(var.key);
        } else {
            loadable_vars.push((var.key, var.value));
        }
    }

    Ok((loadable_vars, protected_keys))
}

/// Set an environment variable for a project
#[allow(clippy::too_many_arguments)]
pub async fn set_env(
    http_client: &Client,
    backend_url: &str,
    token: &str,
    project: &str,
    key: &str,
    value: &str,
    is_secret: bool,
    is_protected: bool,
) -> Result<()> {
    let url = format!("{}/api/v1/projects/{}/env/{}", backend_url, project, key);

    let payload = SetEnvVarRequest {
        value: value.to_string(),
        is_secret,
        is_protected,
    };

    let response = http_client
        .put(&url)
        .header("Authorization", format!("Bearer {}", token))
        .json(&payload)
        .send()
        .await
        .context("Failed to set environment variable")?;

    if !response.status().is_success() {
        let status = response.status();
        let error_text = response
            .text()
            .await
            .unwrap_or_else(|_| "Unknown error".to_string());
        anyhow::bail!(
            "Failed to set environment variable (status {}): {}",
            status,
            error_text
        );
    }

    let var_type = if is_secret {
        if is_protected {
            "protected secret"
        } else {
            "unprotected secret"
        }
    } else {
        "plain text"
    };
    println!(
        "✓ Set {} variable '{}' for project '{}'",
        var_type, key, project
    );

    Ok(())
}

/// List environment variables for a project
pub async fn list_env(
    http_client: &Client,
    backend_url: &str,
    token: &str,
    project: &str,
) -> Result<()> {
    let env_vars_response =
        fetch_env_vars_response(http_client, backend_url, token, project).await?;

    if env_vars_response.env_vars.is_empty() {
        println!(
            "No environment variables configured for project '{}'",
            project
        );
        return Ok(());
    }

    let mut table = Table::new();
    table
        .load_preset(UTF8_FULL)
        .apply_modifier(UTF8_ROUND_CORNERS)
        .set_header(vec![
            Cell::new("KEY").add_attribute(Attribute::Bold),
            Cell::new("VALUE").add_attribute(Attribute::Bold),
            Cell::new("TYPE").add_attribute(Attribute::Bold),
            Cell::new("PROTECTED").add_attribute(Attribute::Bold),
        ]);

    for var in env_vars_response.env_vars {
        let var_type = if var.is_secret { "secret" } else { "plain" };
        let protected = if var.is_secret {
            if var.is_protected {
                "yes"
            } else {
                "no"
            }
        } else {
            "-"
        };
        table.add_row(vec![
            Cell::new(&var.key),
            Cell::new(&var.value),
            Cell::new(var_type),
            Cell::new(protected),
        ]);
    }

    println!("{}", table);
    println!("\nProject: {}", project);
    println!("Note: Secret values are always masked for security");

    Ok(())
}

/// Get the value of a specific environment variable
pub async fn get_env(
    http_client: &Client,
    backend_url: &str,
    token: &str,
    project: &str,
    key: &str,
) -> Result<()> {
    // First, fetch the variable to check if it exists and get its metadata
    let env_vars_response =
        fetch_env_vars_response(http_client, backend_url, token, project).await?;

    let env_var = env_vars_response
        .env_vars
        .into_iter()
        .find(|v| v.key == key)
        .ok_or_else(|| anyhow::anyhow!("Environment variable '{}' not found", key))?;

    // If it's a secret and protected, we can't get the value
    if env_var.is_secret && env_var.is_protected {
        anyhow::bail!(
            "Cannot retrieve value: '{}' is a protected secret.\n\
             To make it unprotected, update it with: rise env set {} <value> --secret --protected=false",
            key, key
        );
    }

    // If it's an unprotected secret, fetch the decrypted value
    if env_var.is_secret && !env_var.is_protected {
        let url = format!(
            "{}/api/v1/projects/{}/env/{}/value",
            backend_url, project, key
        );

        let response = http_client
            .get(&url)
            .header("Authorization", format!("Bearer {}", token))
            .send()
            .await
            .context("Failed to get environment variable value")?;

        if !response.status().is_success() {
            let status = response.status();
            let error_text = response
                .text()
                .await
                .unwrap_or_else(|_| "Unknown error".to_string());
            anyhow::bail!(
                "Failed to get environment variable value (status {}): {}",
                status,
                error_text
            );
        }

        #[derive(Debug, serde::Deserialize)]
        struct EnvVarValueResponse {
            value: String,
        }

        let value_response: EnvVarValueResponse = response
            .json()
            .await
            .context("Failed to parse environment variable value response")?;

        // Print just the value (useful for scripting)
        println!("{}", value_response.value);
    } else {
        // For non-secret variables, the value is already in the response
        println!("{}", env_var.value);
    }

    Ok(())
}

/// Delete an environment variable from a project
pub async fn unset_env(
    http_client: &Client,
    backend_url: &str,
    token: &str,
    project: &str,
    key: &str,
) -> Result<()> {
    let url = format!("{}/api/v1/projects/{}/env/{}", backend_url, project, key);

    let response = http_client
        .delete(&url)
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .context("Failed to delete environment variable")?;

    if !response.status().is_success() {
        let status = response.status();
        let error_text = response
            .text()
            .await
            .unwrap_or_else(|_| "Unknown error".to_string());
        anyhow::bail!(
            "Failed to delete environment variable (status {}): {}",
            status,
            error_text
        );
    }

    println!("✓ Deleted variable '{}' from project '{}'", key, project);

    Ok(())
}

/// A parsed environment variable from a file or string
#[derive(Debug, Clone)]
pub struct ParsedEnvVar {
    pub key: String,
    pub value: String,
    pub is_secret: bool,
}

fn iter_env_file_lines(contents: &str) -> impl Iterator<Item = (usize, &str)> {
    contents.lines().enumerate().filter_map(|(line_num, line)| {
        let line = line.trim();
        if line.is_empty() || line.starts_with('#') {
            None
        } else {
            Some((line_num + 1, line))
        }
    })
}

/// Parse a single KEY=VALUE or KEY=secret:VALUE string
pub fn parse_env_string(s: &str) -> anyhow::Result<ParsedEnvVar> {
    let parts: Vec<&str> = s.splitn(2, '=').collect();
    if parts.len() != 2 {
        anyhow::bail!("Invalid format (expected KEY=value): {}", s);
    }

    let key = parts[0].trim();
    let value_part = parts[1];

    // Validate key name
    if key.is_empty() {
        anyhow::bail!("Invalid key name '' (must be alphanumeric with underscores)");
    }

    if !key.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') {
        anyhow::bail!(
            "Invalid key name '{}' (must be alphanumeric with underscores)",
            key
        );
    }

    let (value, is_secret) = if let Some(stripped) = value_part.strip_prefix("secret:") {
        (stripped, true)
    } else {
        (value_part, false)
    };

    Ok(ParsedEnvVar {
        key: key.to_string(),
        value: value.to_string(),
        is_secret,
    })
}

/// Parse a multi-line env file (same format as `rise env import`)
///
/// Lines starting with # are comments, empty lines are ignored.
/// Format: KEY=value (plain text) or KEY=secret:value (secret)
pub fn parse_env_file(contents: &str) -> anyhow::Result<Vec<ParsedEnvVar>> {
    let mut vars = Vec::new();

    for (line_num, line) in iter_env_file_lines(contents) {
        match parse_env_string(line) {
            Ok(var) => vars.push(var),
            Err(e) => anyhow::bail!("Line {}: {}", line_num, e),
        }
    }

    Ok(vars)
}

/// Import environment variables from a file
///
/// File format:
/// - Lines starting with # are comments
/// - Empty lines are ignored
/// - Format: KEY=value (plain text) or KEY=secret:value (secret)
/// - Example:
///   ```
///   # Database configuration
///   DB_HOST=localhost
///   DB_PASSWORD=secret:my-secret-password
///   ```
pub async fn import_env(
    http_client: &Client,
    backend_url: &str,
    token: &str,
    project: &str,
    file_path: &PathBuf,
) -> Result<()> {
    let contents = std::fs::read_to_string(file_path)
        .with_context(|| format!("Failed to read file: {}", file_path.display()))?;

    let mut success_count = 0;
    let mut error_count = 0;

    for (line_num, line) in iter_env_file_lines(&contents) {
        let parsed = match parse_env_string(line) {
            Ok(parsed) => parsed,
            Err(e) => {
                eprintln!("Warning: Line {}: {}", line_num, e);
                error_count += 1;
                continue;
            }
        };

        // Set the variable
        // Protected defaults to true for secrets, false for non-secrets
        let is_protected = parsed.is_secret;
        match set_env(
            http_client,
            backend_url,
            token,
            project,
            &parsed.key,
            &parsed.value,
            parsed.is_secret,
            is_protected,
        )
        .await
        {
            Ok(_) => success_count += 1,
            Err(e) => {
                eprintln!(
                    "Warning: Failed to set variable '{}' from line {}: {}",
                    parsed.key, line_num, e
                );
                error_count += 1;
            }
        }
    }

    println!(
        "\n✓ Import complete: {} variables set, {} errors",
        success_count, error_count
    );

    if error_count > 0 {
        anyhow::bail!("Import completed with {} errors", error_count);
    }

    Ok(())
}

/// List environment variables for a deployment (read-only)
pub async fn list_deployment_env(
    http_client: &Client,
    backend_url: &str,
    token: &str,
    project: &str,
    deployment_id: &str,
) -> Result<()> {
    let url = format!(
        "{}/api/v1/projects/{}/deployments/{}/env",
        backend_url, project, deployment_id
    );

    let response = http_client
        .get(&url)
        .header("Authorization", format!("Bearer {}", token))
        .send()
        .await
        .context("Failed to list deployment environment variables")?;

    if !response.status().is_success() {
        let status = response.status();
        let error_text = response
            .text()
            .await
            .unwrap_or_else(|_| "Unknown error".to_string());
        anyhow::bail!(
            "Failed to list deployment environment variables (status {}): {}",
            status,
            error_text
        );
    }

    let env_vars_response: EnvVarsResponse = response
        .json()
        .await
        .context("Failed to parse environment variables response")?;

    if env_vars_response.env_vars.is_empty() {
        println!(
            "No environment variables configured for deployment '{}' in project '{}'",
            deployment_id, project
        );
        return Ok(());
    }

    let mut table = Table::new();
    table
        .load_preset(UTF8_FULL)
        .apply_modifier(UTF8_ROUND_CORNERS)
        .set_header(vec![
            Cell::new("KEY").add_attribute(Attribute::Bold),
            Cell::new("VALUE").add_attribute(Attribute::Bold),
            Cell::new("TYPE").add_attribute(Attribute::Bold),
        ]);

    for var in env_vars_response.env_vars {
        let var_type = if var.is_secret { "secret" } else { "plain" };
        table.add_row(vec![
            Cell::new(&var.key),
            Cell::new(&var.value),
            Cell::new(var_type),
        ]);
    }

    println!("{}", table);
    println!("\nProject: {}", project);
    println!("Deployment: {}", deployment_id);
    println!("Note: Secret values are always masked for security");
    println!("Note: Deployment environment variables are read-only snapshots");

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{parse_env_file, parse_env_string};

    #[test]
    fn parse_env_string_rejects_empty_keys() {
        let err = parse_env_string("=value").unwrap_err();
        assert_eq!(
            err.to_string(),
            "Invalid key name '' (must be alphanumeric with underscores)"
        );
    }

    #[test]
    fn parse_env_file_reports_original_line_numbers() {
        let err = parse_env_file("\n# comment\n=value").unwrap_err();
        assert_eq!(
            err.to_string(),
            "Line 3: Invalid key name '' (must be alphanumeric with underscores)"
        );
    }

    #[test]
    fn parse_env_string_supports_secret_values() {
        let parsed = parse_env_string("API_KEY=secret:value").unwrap();

        assert_eq!(parsed.key, "API_KEY");
        assert_eq!(parsed.value, "value");
        assert!(parsed.is_secret);
    }
}
