use crate::debug::{log, LogLevel};
use crate::licenses::detect_project_license;
use colored::*;
use std::fs;
use std::io::{self, Write};
use std::path::Path;

const FELUDA_TOML: &str = ".feluda.toml";
const PRE_COMMIT_YAML: &str = ".pre-commit-config.yaml";

/// Scan the project directory and return detected language names
fn detect_languages(path: &Path) -> Vec<String> {
    let mut detected: Vec<&'static str> = Vec::new();

    let file_checks: &[(&str, &str)] = &[
        ("Cargo.toml", "Rust"),
        ("package.json", "Node.js"),
        ("go.mod", "Go"),
        ("go.work", "Go"),
        ("pyproject.toml", "Python"),
        ("requirements.txt", "Python"),
        ("Pipfile.lock", "Python"),
        ("pip_freeze.txt", "Python"),
        ("pom.xml", "Java/Maven"),
        ("build.gradle", "Java/Gradle"),
        ("build.gradle.kts", "Java/Gradle"),
        ("CMakeLists.txt", "C++"),
        ("vcpkg.json", "C++"),
        ("conanfile.txt", "C++"),
        ("conanfile.py", "C++"),
        ("MODULE.bazel", "C++"),
        ("configure.ac", "C"),
        ("configure.in", "C"),
        ("DESCRIPTION", "R"),
        ("renv.lock", "R"),
        ("composer.json", "PHP"),
        ("composer.lock", "PHP"),
        ("Gemfile", "Ruby"),
        ("Gemfile.lock", "Ruby"),
    ];

    for (file, lang) in file_checks {
        if path.join(file).exists() && !detected.contains(lang) {
            detected.push(lang);
        }
    }

    // .NET: scan for project files by extension
    if let Ok(entries) = fs::read_dir(path) {
        for entry in entries.flatten() {
            let name = entry.file_name();
            let name_str = name.to_string_lossy().to_string();
            if (name_str.ends_with(".csproj")
                || name_str.ends_with(".fsproj")
                || name_str.ends_with(".vbproj")
                || name_str.ends_with(".slnx"))
                && !detected.contains(&"C#/.NET")
            {
                detected.push("C#/.NET");
            }
        }
    }

    detected.iter().map(|s| s.to_string()).collect()
}

/// Check if the pre-commit YAML already contains a feluda hook
fn pre_commit_has_feluda(content: &str) -> bool {
    content.contains("feluda-license-check") || content.contains("entry: feluda")
}

/// Generate the content for .feluda.toml
fn generate_feluda_toml(project_license: Option<&str>) -> String {
    let license_comment = match project_license {
        Some(lic) => format!(
            "# Project license detected: {lic}\n# Dependencies are checked for compatibility against this license.\n"
        ),
        None => "# Set your project license here for compatibility checking:\n# project_license = \"MIT\"\n".to_string(),
    };

    format!(
        r#"# Feluda configuration — generated by `feluda init`
# Documentation: https://github.com/anistark/feluda

{license_comment}
[licenses]
# Licenses flagged as restrictive. Dependencies using these will be highlighted.
# AI coding tools (Cursor, Copilot, Windsurf) can silently pull in GPL/AGPL deps —
# keeping this list tight catches those before they reach production.
restrictive = [
    "GPL-3.0",
    "AGPL-3.0",
    "LGPL-3.0",
    "MPL-2.0",
    "CC-BY-SA-4.0",
    "EPL-2.0",
]

# Licenses to skip from the scan entirely (e.g. internal or pre-approved deps).
ignore = []

[dependencies]
# Maximum depth for transitive dependency resolution (1–100).
max_depth = 10

# To exclude a specific dependency from scanning, uncomment and fill in:
# [[dependencies.ignore]]
# name = "some-package"
# version = ""   # leave empty to ignore all versions
# reason = "Why this dependency is excluded"
"#
    )
}

/// Generate a fresh .pre-commit-config.yaml with the feluda hook
fn generate_pre_commit_yaml() -> String {
    r#"# .pre-commit-config.yaml — generated by `feluda init`
# Activate with: pre-commit install
# See https://pre-commit.com for more information.

repos:
  - repo: local
    hooks:
      - id: feluda-license-check
        name: Feluda License Check
        description: "Scan dependencies for restrictive or incompatible licenses (catches GPL/AGPL deps added by AI coding tools)"
        language: system
        entry: feluda
        args:
          - "--fail-on-restrictive"
        pass_filenames: false
        always_run: true
"#
    .to_string()
}

/// The block appended when merging into an existing .pre-commit-config.yaml
fn pre_commit_feluda_block() -> &'static str {
    r#"
  # Added by `feluda init`
  - repo: local
    hooks:
      - id: feluda-license-check
        name: Feluda License Check
        description: "Scan dependencies for restrictive or incompatible licenses"
        language: system
        entry: feluda
        args:
          - "--fail-on-restrictive"
        pass_filenames: false
        always_run: true
"#
}

fn write_feluda_toml(toml_path: &Path, project_license: Option<&str>) {
    let content = generate_feluda_toml(project_license);
    match fs::write(toml_path, &content) {
        Ok(_) => println!(
            "  {} Created {}",
            "✓".green().bold(),
            FELUDA_TOML.bright_white()
        ),
        Err(e) => {
            println!(
                "  {} Failed to write {}: {}",
                "✗".red().bold(),
                FELUDA_TOML,
                e
            );
            log(
                LogLevel::Error,
                &format!("Failed to write {FELUDA_TOML}: {e}"),
            );
        }
    }
}

fn write_pre_commit_yaml(yaml_path: &Path) {
    let content = generate_pre_commit_yaml();
    match fs::write(yaml_path, &content) {
        Ok(_) => println!(
            "  {} Created {}",
            "✓".green().bold(),
            PRE_COMMIT_YAML.bright_white()
        ),
        Err(e) => {
            println!(
                "  {} Failed to write {}: {}",
                "✗".red().bold(),
                PRE_COMMIT_YAML,
                e
            );
            log(
                LogLevel::Error,
                &format!("Failed to write {PRE_COMMIT_YAML}: {e}"),
            );
        }
    }
}

fn merge_pre_commit_yaml(yaml_path: &Path) {
    match fs::read_to_string(yaml_path) {
        Ok(existing) => {
            if pre_commit_has_feluda(&existing) {
                println!(
                    "  {} {} already contains a feluda hook — skipped.",
                    "ℹ".blue().bold(),
                    PRE_COMMIT_YAML
                );
            } else {
                let merged = format!("{}{}", existing.trim_end(), pre_commit_feluda_block());
                match fs::write(yaml_path, merged) {
                    Ok(_) => println!(
                        "  {} Updated {} (feluda hook appended)",
                        "✓".green().bold(),
                        PRE_COMMIT_YAML.bright_white()
                    ),
                    Err(e) => {
                        println!(
                            "  {} Failed to update {}: {}",
                            "✗".red().bold(),
                            PRE_COMMIT_YAML,
                            e
                        );
                        log(
                            LogLevel::Error,
                            &format!("Failed to update {PRE_COMMIT_YAML}: {e}"),
                        );
                    }
                }
            }
        }
        Err(e) => {
            println!(
                "  {} Could not read {}: {}",
                "✗".red().bold(),
                PRE_COMMIT_YAML,
                e
            );
        }
    }
}

fn ask_yes_no(prompt: &str, default_yes: bool) -> bool {
    let hint = if default_yes { "[Y/n]" } else { "[y/N]" };
    print!("{} {}: ", prompt, hint.dimmed());
    io::stdout().flush().unwrap();

    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();
    let trimmed = input.trim().to_lowercase();

    if trimmed.is_empty() {
        return default_yes;
    }
    matches!(trimmed.as_str(), "y" | "yes")
}

/// Entry point for `feluda init`
pub fn handle_init_command(path: String, force: bool, no_pre_commit: bool) {
    log(
        LogLevel::Info,
        &format!("Starting init command at path: {path}"),
    );

    println!(
        "\n{}",
        "┌─────────────────────────────────────────────┐".bright_cyan()
    );
    println!(
        "{}",
        "│          feluda init — project setup         │"
            .bright_cyan()
            .bold()
    );
    println!(
        "{}",
        "└─────────────────────────────────────────────┘".bright_cyan()
    );
    println!();

    let base_path = Path::new(&path);

    // ── Language detection ──────────────────────────────────────────────────
    let languages = detect_languages(base_path);
    if languages.is_empty() {
        println!(
            "{} {}",
            "→".cyan(),
            "No recognized project files found (defaults will be used).".dimmed()
        );
    } else {
        println!("{} Detected: {}", "→".cyan(), languages.join(", ").yellow());
    }

    // ── License detection ───────────────────────────────────────────────────
    let project_license = match detect_project_license(&path) {
        Ok(Some(lic)) => {
            println!("{} Project license: {}", "→".cyan(), lic.yellow());
            Some(lic)
        }
        _ => {
            println!(
                "{} {}",
                "→".cyan(),
                "Project license: not detected.".dimmed()
            );
            None
        }
    };

    println!();

    // ── .feluda.toml ────────────────────────────────────────────────────────
    let toml_path = base_path.join(FELUDA_TOML);

    if toml_path.exists() && !force {
        if ask_yes_no(
            &format!(
                "{} {} already exists. Overwrite?",
                "⚠".yellow().bold(),
                FELUDA_TOML
            ),
            false,
        ) {
            write_feluda_toml(&toml_path, project_license.as_deref());
        } else {
            println!("  {} Skipped {}.", "·".dimmed(), FELUDA_TOML);
        }
    } else {
        write_feluda_toml(&toml_path, project_license.as_deref());
    }

    // ── .pre-commit-config.yaml ─────────────────────────────────────────────
    if !no_pre_commit {
        let yaml_path = base_path.join(PRE_COMMIT_YAML);

        if yaml_path.exists() {
            if force
                || ask_yes_no(
                    &format!(
                        "{} {} exists. Add feluda hook?",
                        "→".cyan(),
                        PRE_COMMIT_YAML
                    ),
                    true,
                )
            {
                merge_pre_commit_yaml(&yaml_path);
            } else {
                println!("  {} Skipped {}.", "·".dimmed(), PRE_COMMIT_YAML);
            }
        } else if force || ask_yes_no(&format!("{} Create {}?", "→".cyan(), PRE_COMMIT_YAML), true)
        {
            write_pre_commit_yaml(&yaml_path);
        } else {
            println!("  {} Skipped {}.", "·".dimmed(), PRE_COMMIT_YAML);
        }
    }

    // ── Next steps ──────────────────────────────────────────────────────────
    println!();
    println!("{}", "Next steps:".bold());
    println!(
        "  {}  Run {} to scan your project",
        "1.".dimmed(),
        "feluda".bright_white()
    );
    if !no_pre_commit {
        println!(
            "  {}  Run {} to activate the pre-commit hook",
            "2.".dimmed(),
            "pre-commit install".bright_white()
        );
        println!(
            "  {}  Edit {} to customise restrictive licenses or add ignore rules",
            "3.".dimmed(),
            FELUDA_TOML.bright_white()
        );
    } else {
        println!(
            "  {}  Edit {} to customise restrictive licenses or add ignore rules",
            "2.".dimmed(),
            FELUDA_TOML.bright_white()
        );
    }
    println!();
    println!("{}", "Docs: https://github.com/anistark/feluda".dimmed());
    println!();

    log(LogLevel::Info, "Init command completed");
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::TempDir;

    #[test]
    fn test_detect_languages_empty_dir() {
        let dir = TempDir::new().unwrap();
        let langs = detect_languages(dir.path());
        assert!(langs.is_empty());
    }

    #[test]
    fn test_detect_languages_rust() {
        let dir = TempDir::new().unwrap();
        fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"test\"").unwrap();
        let langs = detect_languages(dir.path());
        assert!(langs.contains(&"Rust".to_string()));
    }

    #[test]
    fn test_detect_languages_node() {
        let dir = TempDir::new().unwrap();
        fs::write(dir.path().join("package.json"), "{}").unwrap();
        let langs = detect_languages(dir.path());
        assert!(langs.contains(&"Node.js".to_string()));
    }

    #[test]
    fn test_detect_languages_python() {
        let dir = TempDir::new().unwrap();
        fs::write(dir.path().join("requirements.txt"), "requests==2.0").unwrap();
        let langs = detect_languages(dir.path());
        assert!(langs.contains(&"Python".to_string()));
    }

    #[test]
    fn test_detect_languages_go() {
        let dir = TempDir::new().unwrap();
        fs::write(dir.path().join("go.mod"), "module example.com/m").unwrap();
        let langs = detect_languages(dir.path());
        assert!(langs.contains(&"Go".to_string()));
    }

    #[test]
    fn test_detect_languages_dotnet() {
        let dir = TempDir::new().unwrap();
        fs::write(dir.path().join("MyApp.csproj"), "<Project />").unwrap();
        let langs = detect_languages(dir.path());
        assert!(langs.contains(&"C#/.NET".to_string()));
    }

    #[test]
    fn test_detect_languages_multi() {
        let dir = TempDir::new().unwrap();
        fs::write(dir.path().join("Cargo.toml"), "[package]\nname = \"t\"").unwrap();
        fs::write(dir.path().join("package.json"), "{}").unwrap();
        let langs = detect_languages(dir.path());
        assert!(langs.contains(&"Rust".to_string()));
        assert!(langs.contains(&"Node.js".to_string()));
    }

    #[test]
    fn test_go_deduplication() {
        let dir = TempDir::new().unwrap();
        fs::write(dir.path().join("go.mod"), "module x").unwrap();
        fs::write(dir.path().join("go.work"), "go 1.21").unwrap();
        let langs = detect_languages(dir.path());
        // Go should appear exactly once
        assert_eq!(langs.iter().filter(|l| l.as_str() == "Go").count(), 1);
    }

    #[test]
    fn test_pre_commit_has_feluda_true() {
        let content = "repos:\n  - repo: local\n    hooks:\n      - id: feluda-license-check\n";
        assert!(pre_commit_has_feluda(content));
    }

    #[test]
    fn test_pre_commit_has_feluda_false() {
        let content = "repos:\n  - repo: local\n    hooks:\n      - id: other-hook\n";
        assert!(!pre_commit_has_feluda(content));
    }

    #[test]
    fn test_generate_feluda_toml_with_license() {
        let content = generate_feluda_toml(Some("MIT"));
        assert!(content.contains("MIT"));
        assert!(content.contains("GPL-3.0"));
        assert!(content.contains("AGPL-3.0"));
        assert!(content.contains("restrictive"));
        assert!(content.contains("max_depth"));
    }

    #[test]
    fn test_generate_feluda_toml_without_license() {
        let content = generate_feluda_toml(None);
        assert!(content.contains("project_license"));
        assert!(content.contains("GPL-3.0"));
    }

    #[test]
    fn test_generate_pre_commit_yaml() {
        let content = generate_pre_commit_yaml();
        assert!(content.contains("feluda-license-check"));
        assert!(content.contains("entry: feluda"));
        assert!(content.contains("--fail-on-restrictive"));
        assert!(content.contains("pass_filenames: false"));
    }

    #[test]
    fn test_write_feluda_toml_creates_file() {
        let dir = TempDir::new().unwrap();
        let toml_path = dir.path().join(FELUDA_TOML);
        write_feluda_toml(&toml_path, Some("Apache-2.0"));
        assert!(toml_path.exists());
        let content = fs::read_to_string(&toml_path).unwrap();
        assert!(content.contains("Apache-2.0"));
        assert!(content.contains("GPL-3.0"));
    }

    #[test]
    fn test_write_pre_commit_yaml_creates_file() {
        let dir = TempDir::new().unwrap();
        let yaml_path = dir.path().join(PRE_COMMIT_YAML);
        write_pre_commit_yaml(&yaml_path);
        assert!(yaml_path.exists());
        let content = fs::read_to_string(&yaml_path).unwrap();
        assert!(content.contains("feluda-license-check"));
    }

    #[test]
    fn test_merge_pre_commit_yaml_adds_hook() {
        let dir = TempDir::new().unwrap();
        let yaml_path = dir.path().join(PRE_COMMIT_YAML);
        fs::write(
            &yaml_path,
            "repos:\n  - repo: local\n    hooks:\n      - id: other-hook\n        entry: other\n",
        )
        .unwrap();
        merge_pre_commit_yaml(&yaml_path);
        let content = fs::read_to_string(&yaml_path).unwrap();
        assert!(content.contains("feluda-license-check"));
        assert!(content.contains("other-hook"));
    }

    #[test]
    fn test_merge_pre_commit_yaml_skips_if_present() {
        let dir = TempDir::new().unwrap();
        let yaml_path = dir.path().join(PRE_COMMIT_YAML);
        let original = "repos:\n  - repo: local\n    hooks:\n      - id: feluda-license-check\n        entry: feluda\n";
        fs::write(&yaml_path, original).unwrap();
        merge_pre_commit_yaml(&yaml_path);
        // File should be unchanged
        let content = fs::read_to_string(&yaml_path).unwrap();
        assert_eq!(content, original);
    }

    #[test]
    fn test_handle_init_command_no_pre_commit() {
        let dir = TempDir::new().unwrap();
        let path = dir.path().to_str().unwrap().to_string();
        // Should not panic; no interactive prompts triggered since force=true
        handle_init_command(path, true, true);
        assert!(dir.path().join(FELUDA_TOML).exists());
        assert!(!dir.path().join(PRE_COMMIT_YAML).exists());
    }

    #[test]
    fn test_handle_init_command_force_creates_both() {
        let dir = TempDir::new().unwrap();
        let path = dir.path().to_str().unwrap().to_string();
        handle_init_command(path, true, false);
        assert!(dir.path().join(FELUDA_TOML).exists());
        assert!(dir.path().join(PRE_COMMIT_YAML).exists());
    }

    #[test]
    fn test_handle_init_command_force_overwrites_toml() {
        let dir = TempDir::new().unwrap();
        let path = dir.path().to_str().unwrap().to_string();
        let toml_path = dir.path().join(FELUDA_TOML);
        fs::write(&toml_path, "old content").unwrap();
        handle_init_command(path, true, true);
        let content = fs::read_to_string(&toml_path).unwrap();
        assert!(!content.contains("old content"));
        assert!(content.contains("GPL-3.0"));
    }

    #[test]
    fn test_handle_init_command_force_merges_pre_commit() {
        let dir = TempDir::new().unwrap();
        let path = dir.path().to_str().unwrap().to_string();
        let yaml_path = dir.path().join(PRE_COMMIT_YAML);
        fs::write(
            &yaml_path,
            "repos:\n  - repo: local\n    hooks:\n      - id: other-hook\n",
        )
        .unwrap();
        handle_init_command(path, true, false);
        let content = fs::read_to_string(&yaml_path).unwrap();
        assert!(content.contains("feluda-license-check"));
        assert!(content.contains("other-hook"));
    }
}
