use std::collections::HashMap;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};

use anyhow::Result;
use ignore::WalkBuilder;

const CYAN: &str = "\x1b[36m";
const GREEN: &str = "\x1b[32m";
const YELLOW: &str = "\x1b[33m";
const RESET: &str = "\x1b[0m";
const SEPARATOR: &str = "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━";

pub fn run(path: Option<PathBuf>) -> Result<()> {
    let path = path.unwrap_or_else(|| env::current_dir().unwrap());
    let mut languages: HashMap<&str, (usize, usize)> = HashMap::new();
    let mut livewire_php_lines: usize = 0;
    let mut livewire_blade_lines: usize = 0;
    let mut livewire_files: usize = 0;
    let mut test_lines: usize = 0;
    let mut test_files: usize = 0;

    for entry in WalkBuilder::new(&path).build() {
        let entry = entry?;
        if !entry.file_type().map_or(false, |ft| ft.is_file()) {
            continue;
        }

        let file_path = entry.path();
        let filename = file_path.file_name().unwrap_or_default().to_string_lossy();

        if is_lock_file(&filename) {
            continue;
        }

        // Livewire SFC: ⚡*.blade.php (excluding test files)
        if filename.starts_with('⚡')
            && filename.ends_with(".blade.php")
            && !filename.ends_with(".test.php")
        {
            let Ok(content) = fs::read_to_string(file_path) else {
                continue;
            };
            livewire_files += 1;
            let mut in_php = true;
            for line in content.lines() {
                if in_php && line.trim() == "?>" {
                    in_php = false;
                } else if in_php {
                    livewire_php_lines += 1;
                } else {
                    livewire_blade_lines += 1;
                }
            }
            continue;
        }

        // PHP tests: *.test.php or .php in tests/ directory
        let in_tests_dir = file_path.components().any(|c| c.as_os_str() == "tests");
        if filename.ends_with(".test.php") || (in_tests_dir && filename.ends_with(".php")) {
            let Ok(content) = fs::read_to_string(file_path) else {
                continue;
            };
            test_lines += content.lines().count();
            test_files += 1;
            continue;
        }

        // Blade templates: *.blade.php (not Livewire SFC)
        if filename.ends_with(".blade.php") {
            let Ok(content) = fs::read_to_string(file_path) else {
                continue;
            };
            let entry = languages.entry("Blade").or_insert((0, 0));
            entry.0 += content.lines().count();
            entry.1 += 1;
            continue;
        }

        if let Some(lang) = detect_language(file_path) {
            let Ok(content) = fs::read_to_string(file_path) else {
                continue;
            };
            let entry = languages.entry(lang).or_insert((0, 0));
            entry.0 += content.lines().count();
            entry.1 += 1;
        }
    }

    let mut sorted: Vec<_> = languages.into_iter().collect();
    sorted.sort_by(|a, b| b.1 .0.cmp(&a.1 .0));

    println!("{CYAN}📊 Statistiques du code{RESET}");
    println!("{SEPARATOR}");

    let mut total_lines: usize = 0;
    let mut total_files: usize = 0;

    if livewire_files > 0 {
        print_line(
            GREEN,
            "Livewire SFC (PHP)",
            livewire_php_lines,
            Some(livewire_files),
        );
        print_line(GREEN, "Livewire SFC (Blade)", livewire_blade_lines, None);
        total_lines += livewire_php_lines + livewire_blade_lines;
        total_files += livewire_files;
    }

    for (lang, (lines, files)) in &sorted {
        print_line(GREEN, lang, *lines, Some(*files));
        total_lines += lines;
        total_files += files;
    }

    if test_files > 0 {
        print_line(YELLOW, "Tests", test_lines, Some(test_files));
    }

    println!("{SEPARATOR}");
    print_line(CYAN, "Total (hors tests)", total_lines, Some(total_files));
    print_line(
        CYAN,
        "Total",
        total_lines + test_lines,
        Some(total_files + test_files),
    );

    Ok(())
}

fn print_line(color: &str, name: &str, lines: usize, files: Option<usize>) {
    match files {
        Some(f) => println!(
            "{color}{:<25}{RESET} {:>6} lignes  ({f} fichiers)",
            name, lines
        ),
        None => println!("{color}{:<25}{RESET} {:>6} lignes", name, lines),
    }
}

fn is_lock_file(filename: &str) -> bool {
    matches!(
        filename,
        "composer.lock"
            | "package-lock.json"
            | "yarn.lock"
            | "pnpm-lock.yaml"
            | "Cargo.lock"
            | "Gemfile.lock"
            | "poetry.lock"
            | "flake.lock"
    )
}

fn detect_language(path: &Path) -> Option<&'static str> {
    let filename = path.file_name()?.to_str()?;
    match filename {
        "Dockerfile" | "Containerfile" => return Some("Docker"),
        "Makefile" | "GNUmakefile" => return Some("Makefile"),
        _ => {}
    }

    let ext = path.extension()?.to_str()?;
    match ext {
        "php" => Some("PHP"),
        "js" | "mjs" | "cjs" => Some("JavaScript"),
        "ts" | "mts" | "cts" => Some("TypeScript"),
        "jsx" => Some("JSX"),
        "tsx" => Some("TSX"),
        "rs" => Some("Rust"),
        "py" | "pyw" => Some("Python"),
        "rb" => Some("Ruby"),
        "go" => Some("Go"),
        "java" => Some("Java"),
        "kt" | "kts" => Some("Kotlin"),
        "swift" => Some("Swift"),
        "c" | "h" => Some("C"),
        "cpp" | "cc" | "cxx" | "hpp" | "hxx" | "hh" => Some("C++"),
        "cs" => Some("C#"),
        "hs" | "lhs" => Some("Haskell"),
        "ex" | "exs" => Some("Elixir"),
        "erl" | "hrl" => Some("Erlang"),
        "dart" => Some("Dart"),
        "lua" => Some("Lua"),
        "r" | "R" => Some("R"),
        "scala" | "sc" => Some("Scala"),
        "zig" => Some("Zig"),
        "sh" | "bash" | "zsh" | "fish" => Some("Shell"),
        "sql" => Some("SQL"),
        "vue" => Some("Vue"),
        "svelte" => Some("Svelte"),
        _ => None,
    }
}
