use std::io::{self, IsTerminal, Read};
use std::process::ExitCode;

use clap::{Parser, ValueEnum};
use rustyline::error::ReadlineError;
use rustyline::DefaultEditor;

use calcpad_engine::{eval_line, eval_sheet, CalcResult, CalcValue, EvalContext, ResultType};

// ---------------------------------------------------------------------------
// CLI argument parsing
// ---------------------------------------------------------------------------

#[derive(Debug, Clone, Copy, ValueEnum)]
enum OutputFormat {
    Plain,
    Json,
    Csv,
}

#[derive(Parser, Debug)]
#[command(
    name = "calcpad",
    about = "CalcPad — notepad-style calculator",
    long_about = "Evaluate mathematical expressions, unit conversions, and more.\n\n\
        Examples:\n  \
        calcpad \"2 + 3 * 4\"\n  \
        calcpad \"5kg in lbs\"\n  \
        echo \"100 + 200\" | calcpad\n  \
        calcpad --repl"
)]
struct Cli {
    /// Expression to evaluate (omit for stdin/pipe mode).
    expression: Option<String>,

    /// Start an interactive REPL session.
    #[arg(long)]
    repl: bool,

    /// Output format.
    #[arg(long, value_enum, default_value_t = OutputFormat::Plain)]
    format: OutputFormat,
}

// ---------------------------------------------------------------------------
// Result helpers
// ---------------------------------------------------------------------------

fn is_error(result: &CalcResult) -> bool {
    result.metadata.result_type == ResultType::Error
}

fn is_empty_error(result: &CalcResult) -> bool {
    matches!(
        result.metadata.result_type,
        ResultType::Empty | ResultType::NonCalculable
    )
}

fn error_message(result: &CalcResult) -> Option<&str> {
    if let CalcValue::Error { ref message, .. } = result.value {
        Some(message)
    } else {
        None
    }
}

// ---------------------------------------------------------------------------
// Output formatting
// ---------------------------------------------------------------------------

fn print_result_json(result: &CalcResult) {
    // CalcResult derives Serialize via the engine, so we can emit it directly.
    match serde_json::to_string(result) {
        Ok(json) => println!("{}", json),
        Err(e) => eprintln!("Error: failed to serialize result: {}", e),
    }
}

fn print_results_json(results: &[CalcResult]) {
    match serde_json::to_string_pretty(results) {
        Ok(json) => println!("{}", json),
        Err(e) => eprintln!("Error: failed to serialize results: {}", e),
    }
}

fn print_result_csv(result: &CalcResult, line_num: usize) {
    if is_empty_error(result) {
        println!("{},,,", line_num);
        return;
    }
    // line,type,display,raw_value
    let rtype = result.metadata.result_type;
    let display = &result.metadata.display;
    // Escape display for CSV (wrap in quotes if it contains comma/quote)
    let escaped = csv_escape(display);
    let raw = result
        .metadata
        .raw_value
        .map(|v| v.to_string())
        .unwrap_or_default();
    println!("{},{},{},{}", line_num, rtype, escaped, raw);
}

fn csv_escape(s: &str) -> String {
    if s.contains(',') || s.contains('"') || s.contains('\n') {
        format!("\"{}\"", s.replace('"', "\"\""))
    } else {
        s.to_string()
    }
}

// ---------------------------------------------------------------------------
// Modes
// ---------------------------------------------------------------------------

fn run_single_expression(expression: &str, format: OutputFormat) -> ExitCode {
    let mut ctx = EvalContext::new();
    let result = eval_line(expression, &mut ctx);

    if is_empty_error(&result) {
        return ExitCode::SUCCESS;
    }

    match format {
        OutputFormat::Plain => {
            if is_error(&result) {
                if let Some(msg) = error_message(&result) {
                    eprintln!("Error: {}", msg);
                }
                return ExitCode::from(1);
            }
            println!("{}", result.metadata.display);
        }
        OutputFormat::Json => {
            print_result_json(&result);
            if is_error(&result) {
                return ExitCode::from(1);
            }
        }
        OutputFormat::Csv => {
            println!("line,type,display,raw_value");
            print_result_csv(&result, 1);
            if is_error(&result) {
                return ExitCode::from(1);
            }
        }
    }

    ExitCode::SUCCESS
}

fn run_stdin(format: OutputFormat) -> ExitCode {
    let mut input = String::new();
    if let Err(e) = io::stdin().read_to_string(&mut input) {
        eprintln!("Error: failed to read stdin: {}", e);
        return ExitCode::from(1);
    }

    let lines: Vec<&str> = input.lines().collect();
    if lines.is_empty() {
        return ExitCode::SUCCESS;
    }

    let mut ctx = EvalContext::new();
    let results = eval_sheet(&lines, &mut ctx);
    let mut has_error = false;

    match format {
        OutputFormat::Plain => {
            for result in &results {
                if is_empty_error(result) {
                    println!();
                } else if is_error(result) {
                    if let Some(msg) = error_message(result) {
                        println!("Error: {}", msg);
                    }
                    has_error = true;
                } else {
                    println!("{}", result.metadata.display);
                }
            }
        }
        OutputFormat::Json => {
            print_results_json(&results);
            has_error = results.iter().any(|r| is_error(r) && !is_empty_error(r));
        }
        OutputFormat::Csv => {
            println!("line,type,display,raw_value");
            for (i, result) in results.iter().enumerate() {
                print_result_csv(result, i + 1);
                if is_error(result) && !is_empty_error(result) {
                    has_error = true;
                }
            }
        }
    }

    if has_error {
        ExitCode::from(1)
    } else {
        ExitCode::SUCCESS
    }
}

fn run_repl(format: OutputFormat) -> ExitCode {
    let mut rl = match DefaultEditor::new() {
        Ok(editor) => editor,
        Err(err) => {
            eprintln!("Error: failed to initialize line editor: {}", err);
            return ExitCode::from(1);
        }
    };

    // Try to load history from ~/.calcpad_history
    let history_path = dirs_history_path();
    if let Some(ref path) = history_path {
        let _ = rl.load_history(path);
    }

    let mut ctx = EvalContext::new();

    loop {
        match rl.readline("> ") {
            Ok(line) => {
                let trimmed = line.trim();
                if is_exit_command(trimmed) {
                    break;
                }

                let _ = rl.add_history_entry(&line);
                let result = eval_line(&line, &mut ctx);

                if is_empty_error(&result) {
                    // Blank or comment — no output.
                    continue;
                }

                match format {
                    OutputFormat::Plain => {
                        if is_error(&result) {
                            if let Some(msg) = error_message(&result) {
                                eprintln!("Error: {}", msg);
                            }
                        } else {
                            println!("{}", result.metadata.display);
                        }
                    }
                    OutputFormat::Json => {
                        print_result_json(&result);
                    }
                    OutputFormat::Csv => {
                        // In REPL mode we don't print a CSV header each time;
                        // just emit one row per expression.
                        let rtype = result.metadata.result_type;
                        let display = csv_escape(&result.metadata.display);
                        let raw = result
                            .metadata
                            .raw_value
                            .map(|v| v.to_string())
                            .unwrap_or_default();
                        println!("{},{},{}", rtype, display, raw);
                    }
                }
            }
            Err(ReadlineError::Eof) => break,
            Err(ReadlineError::Interrupted) => break,
            Err(err) => {
                eprintln!("Error: {}", err);
                return ExitCode::from(1);
            }
        }
    }

    // Save history
    if let Some(ref path) = history_path {
        let _ = rl.save_history(path);
    }

    ExitCode::SUCCESS
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

fn is_exit_command(input: &str) -> bool {
    let t = input.trim();
    t.eq_ignore_ascii_case("exit") || t.eq_ignore_ascii_case("quit")
}

fn dirs_history_path() -> Option<std::path::PathBuf> {
    // Use $HOME/.calcpad_history if available
    std::env::var_os("HOME").map(|h| std::path::PathBuf::from(h).join(".calcpad_history"))
}

// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------

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

    if cli.repl {
        return run_repl(cli.format);
    }

    if let Some(ref expr) = cli.expression {
        return run_single_expression(expr, cli.format);
    }

    // No expression argument — check if stdin is piped / redirected.
    if !io::stdin().is_terminal() {
        return run_stdin(cli.format);
    }

    // Nothing provided — show help.
    // Re-parse with --help to trigger clap's help output.
    eprintln!("Usage: calcpad <EXPRESSION>");
    eprintln!("       calcpad --repl");
    eprintln!("       echo \"100 + 200\" | calcpad");
    eprintln!();
    eprintln!("Run 'calcpad --help' for full usage information.");
    ExitCode::from(1)
}
