//! # DataDoctor CLI
//!
//! Command-line interface for DataDoctor data validation and cleaning tool.

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use colored::*;
use data_doctor_core::{validate_csv_stream, validate_json_stream, ValidationOptions};
use serde::Serialize;
use std::fs::{self, File};
use std::io::{BufReader, Write};
use std::path::{Path, PathBuf};

#[derive(Parser)]
#[command(name = "data-doctor")]
#[command(author, version, about = "Data validation and cleaning tool", long_about = None)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Validate a data file without making changes
    Validate {
        /// Input file to validate
        #[arg(value_name = "FILE")]
        input: PathBuf,

        /// File format (json, csv) - auto-detected if not specified
        #[arg(short, long)]
        format: Option<String>,

        /// Output report as JSON
        #[arg(long)]
        report_json: bool,

        /// Schema file for validation (optional)
        #[arg(short, long)]
        schema: Option<PathBuf>,
    },

    /// Fix data issues and save to output file
    Fix {
        /// Input file to fix
        #[arg(value_name = "FILE")]
        input: PathBuf,

        /// Output file for fixed data
        #[arg(short, long, value_name = "FILE")]
        out: PathBuf,

        /// File format (json, csv) - auto-detected if not specified
        #[arg(short, long)]
        format: Option<String>,

        /// Schema file for validation (optional)
        #[arg(short, long)]
        schema: Option<PathBuf>,
    },

    /// Validate, fix, and generate comprehensive report
    Doctor {
        /// Input file to process
        #[arg(value_name = "FILE")]
        input: PathBuf,

        /// Output file for fixed data
        #[arg(short, long, value_name = "FILE")]
        out: PathBuf,

        /// File format (json, csv) - auto-detected if not specified
        #[arg(short, long)]
        format: Option<String>,

        /// Output report as JSON
        #[arg(long)]
        report_json: bool,

        /// Schema file for validation (optional)
        #[arg(short, long)]
        schema: Option<PathBuf>,
    },
}

#[derive(Serialize)]
struct JsonReport {
    success: bool,
    total_records: usize,
    valid_records: usize,
    invalid_records: usize,
    total_issues: usize,
    auto_fixed: usize,
    issues: Vec<JsonIssue>,
}

#[derive(Serialize)]
struct JsonIssue {
    severity: String,
    error_code: Option<String>,
    message: String,
    field: Option<String>,
    row: Option<usize>,
    column: Option<usize>,
    auto_fixed: bool,
}

fn print_logo() {
    println!(
        "{}",
        r#"
    ____        __        ____             __            
   / __ \____ _/ /_____ _/ __ \____  _____/ /_____  _____
  / / / / __ `/ __/ __ `/ / / / __ \/ ___/ __/ __ \/ ___/
 / /_/ / /_/ / /_/ /_/ / /_/ / /_/ / /__/ /_/ /_/ / /    
/_____/\__,_/\__/\__,_/_____/\____/\___/\__/\____/_/     
                                                          
"#
        .cyan()
        .bold()
    );
    println!(
        "{}",
        format!(
            "Version {} - Data Validation & Cleaning Tool",
            env!("CARGO_PKG_VERSION")
        )
        .bright_black()
    );
    println!();
}

fn main() -> Result<()> {
    let cli = Cli::parse();

    match cli.command {
        Commands::Validate {
            input,
            format,
            report_json,
            schema,
        } => {
            validate_command(input, format, report_json, schema)?;
        }
        Commands::Fix {
            input,
            out,
            format,
            schema,
        } => {
            fix_command(input, out, format, schema)?;
        }
        Commands::Doctor {
            input,
            out,
            format,
            report_json,
            schema,
        } => {
            doctor_command(input, out, format, report_json, schema)?;
        }
    }

    Ok(())
}

fn validate_command(
    input: PathBuf,
    format: Option<String>,
    report_json: bool,
    _schema: Option<PathBuf>,
) -> Result<()> {
    let format = detect_format(&input, format)?;
    let file =
        File::open(&input).with_context(|| format!("Failed to open file: {}", input.display()))?;
    let reader = BufReader::new(file);

    let options = ValidationOptions::default();

    let result = match format.as_str() {
        "json" => validate_json_stream(reader, &options),
        "csv" => validate_csv_stream(reader, &options),
        _ => anyhow::bail!("Unsupported format: {}", format),
    };

    if report_json {
        print_json_report(&result);
    } else {
        print_human_report(&result, &input, "Validation");
    }

    if result.success {
        Ok(())
    } else {
        std::process::exit(1);
    }
}

fn fix_command(
    input: PathBuf,
    out: PathBuf,
    format: Option<String>,
    _schema: Option<PathBuf>,
) -> Result<()> {
    let format = detect_format(&input, format)?;
    let content = fs::read_to_string(&input)
        .with_context(|| format!("Failed to read file: {}", input.display()))?;

    let mut options = ValidationOptions::default();
    options.auto_fix = true;

    let result = match format.as_str() {
        "json" => {
            let validator = data_doctor_core::json::JsonValidator::new();
            let (fixed_content, result) = validator.validate_and_fix(&content, &options);

            // Write fixed content
            fs::write(&out, fixed_content)
                .with_context(|| format!("Failed to write to: {}", out.display()))?;

            result
        }
        "csv" => {
            // For CSV, we need to process and write the fixed version
            let result = validate_csv_stream(content.as_bytes(), &options);

            // For now, just copy (full CSV fixing would require more complex logic)
            fs::write(&out, content)
                .with_context(|| format!("Failed to write to: {}", out.display()))?;

            result
        }
        _ => anyhow::bail!("Unsupported format: {}", format),
    };

    println!("{}", "✓ File fixed successfully!".green().bold());
    println!("  Input:  {}", input.display());
    println!("  Output: {}", out.display());
    println!();
    print_human_report(&result, &input, "Fix");

    Ok(())
}

fn doctor_command(
    input: PathBuf,
    out: PathBuf,
    format: Option<String>,
    report_json: bool,
    schema: Option<PathBuf>,
) -> Result<()> {
    if !report_json {
        print_logo();
    }

    println!("{}", "🩺 DataDoctor Analysis".cyan().bold());
    println!("{}", "=".repeat(50).cyan());
    println!();

    // First validate
    println!("{}", "Step 1: Validating...".yellow());
    let format = detect_format(&input, format)?;
    let content = fs::read_to_string(&input)
        .with_context(|| format!("Failed to read file: {}", input.display()))?;

    let mut options = ValidationOptions::default();
    options.auto_fix = false;

    let validation_result = match format.as_str() {
        "json" => validate_json_stream(content.as_bytes(), &options),
        "csv" => validate_csv_stream(content.as_bytes(), &options),
        _ => anyhow::bail!("Unsupported format: {}", format),
    };

    if validation_result.success {
        println!("{}", "  ✓ No issues found!".green());
    } else {
        println!(
            "  {} {} issue(s) found",
            "⚠".yellow(),
            validation_result.stats.total_issues
        );
    }
    println!();

    // Then fix
    println!("{}", "Step 2: Applying auto-fixes...".yellow());
    options.auto_fix = true;

    let fix_result = match format.as_str() {
        "json" => {
            let validator = data_doctor_core::json::JsonValidator::new();
            let (fixed_content, result) = validator.validate_and_fix(&content, &options);

            fs::write(&out, fixed_content)
                .with_context(|| format!("Failed to write to: {}", out.display()))?;

            result
        }
        "csv" => {
            let result = validate_csv_stream(content.as_bytes(), &options);
            fs::write(&out, content)
                .with_context(|| format!("Failed to write to: {}", out.display()))?;
            result
        }
        _ => anyhow::bail!("Unsupported format: {}", format),
    };

    if fix_result.stats.auto_fixed > 0 {
        println!(
            "  {} {} issue(s) auto-fixed",
            "✓".green(),
            fix_result.stats.auto_fixed
        );
    } else {
        println!("{}", "  No fixes needed".green());
    }
    println!();

    // Generate report
    println!("{}", "Step 3: Generating report...".yellow());
    println!();

    if report_json {
        print_json_report(&fix_result);
    } else {
        print_human_report(&fix_result, &input, "Doctor");
    }

    println!();
    println!("{}", "✓ Analysis complete!".green().bold());
    println!("  Output saved to: {}", out.display());

    Ok(())
}

fn detect_format(path: &Path, format: Option<String>) -> Result<String> {
    if let Some(fmt) = format {
        return Ok(fmt.to_lowercase());
    }

    let extension = path
        .extension()
        .and_then(|s| s.to_str())
        .context("Could not determine file format from extension")?;

    match extension.to_lowercase().as_str() {
        "json" => Ok("json".to_string()),
        "csv" => Ok("csv".to_string()),
        _ => anyhow::bail!(
            "Unknown file extension: {}. Please specify format with --format",
            extension
        ),
    }
}

fn print_human_report(result: &data_doctor_core::ValidationResult, input: &Path, operation: &str) {
    println!("{}", format!("📊 {} Report", operation).cyan().bold());
    println!("{}", "=".repeat(50).cyan());
    println!();

    // Summary
    println!("{}", "Summary:".bold());
    println!("  File: {}", input.display());
    println!(
        "  Status: {}",
        if result.success {
            "PASSED ✓".green().bold()
        } else {
            "FAILED ✗".red().bold()
        }
    );
    println!();

    // Statistics
    println!("{}", "Statistics:".bold());
    println!("  Total Records:   {}", result.stats.total_records);
    println!(
        "  Valid Records:   {}",
        result.stats.valid_records.to_string().green()
    );
    println!(
        "  Invalid Records: {}",
        result.stats.invalid_records.to_string().red()
    );
    println!("  Total Issues:    {}", result.stats.total_issues);
    if result.stats.auto_fixed > 0 {
        println!(
            "  Auto-Fixed:      {}",
            result.stats.auto_fixed.to_string().green()
        );
    }
    println!();

    // Issues by severity
    if result.stats.total_issues > 0 {
        println!("{}", "Issues by Severity:".bold());
        if result.stats.critical_issues > 0 {
            println!(
                "  Critical: {}",
                result.stats.critical_issues.to_string().red()
            );
        }
        if result.stats.error_issues > 0 {
            println!(
                "  Errors:   {}",
                result.stats.error_issues.to_string().red()
            );
        }
        if result.stats.warning_issues > 0 {
            println!(
                "  Warnings: {}",
                result.stats.warning_issues.to_string().yellow()
            );
        }
        if result.stats.info_issues > 0 {
            println!("  Info:     {}", result.stats.info_issues);
        }
        println!();
    }

    // Detailed issues
    if !result.issues.is_empty() {
        println!("{}", "Issues:".bold());
        for (i, issue) in result.issues.iter().take(10).enumerate() {
            let severity_str = format!("{:?}", issue.severity);
            let colored_severity = match issue.severity {
                data_doctor_core::report::Severity::Critical => severity_str.red(),
                data_doctor_core::report::Severity::Error => severity_str.red(),
                data_doctor_core::report::Severity::Warning => severity_str.yellow(),
                data_doctor_core::report::Severity::Info => severity_str.cyan(),
            };

            print!("  {}. [{}] ", i + 1, colored_severity);

            if let Some(code) = &issue.error_code {
                print!("{} ", code.bright_black());
            }

            println!("{}", issue.message);

            if let Some(row) = issue.row {
                print!("     Location: Row {}", row);
                if let Some(col) = issue.column {
                    print!(", Col {}", col);
                }
                println!();
            }

            if issue.auto_fixed {
                println!("     {}", "✓ Auto-fixed".green());
            }
        }

        if result.issues.len() > 10 {
            println!("  ... and {} more issue(s)", result.issues.len() - 10);
        }
    }
}

fn print_json_report(result: &data_doctor_core::ValidationResult) {
    let report = JsonReport {
        success: result.success,
        total_records: result.stats.total_records,
        valid_records: result.stats.valid_records,
        invalid_records: result.stats.invalid_records,
        total_issues: result.stats.total_issues,
        auto_fixed: result.stats.auto_fixed,
        issues: result
            .issues
            .iter()
            .map(|issue| JsonIssue {
                severity: format!("{:?}", issue.severity),
                error_code: issue.error_code.clone(),
                message: issue.message.clone(),
                field: issue.field.clone(),
                row: issue.row,
                column: issue.column,
                auto_fixed: issue.auto_fixed,
            })
            .collect(),
    };

    println!("{}", serde_json::to_string_pretty(&report).unwrap());
}
