use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CloudConnectionStatus {
    pub endpoint: String,
    pub saved: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StatusReport {
    pub schema_version: u32,
    pub generated_at: DateTime<Utc>,
    pub version: String,
    pub setup_report: Option<crate::core::setup_report::SetupReport>,
    pub doctor_compact_passed: u32,
    pub doctor_compact_total: u32,
    pub mcp_targets: Vec<McpTargetStatus>,
    pub rules_targets: Vec<crate::rules_inject::RulesTargetStatus>,
    pub cloud_connection: Option<CloudConnectionStatus>,
    pub sync_outbox: crate::sync_cli::SyncOutboxStatus,
    pub warnings: Vec<String>,
    pub errors: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpTargetStatus {
    pub name: String,
    pub detected: bool,
    pub config_path: String,
    pub state: String,
    pub note: Option<String>,
}

pub fn run_cli(args: &[String]) -> i32 {
    let json = args.iter().any(|a| a == "--json");
    let help = args.iter().any(|a| a == "--help" || a == "-h");
    if help {
        println!("Usage:");
        println!("  nebu-ctx status [--json]");
        return 0;
    }

    match build_status_report() {
        Ok((report, path)) => {
            let text = serde_json::to_string_pretty(&report).unwrap_or_else(|_| "{}".to_string());
            let _ = crate::config_io::write_atomic_with_backup(&path, &text);

            if json {
                println!("{text}");
            } else {
                print_human(&report, &path);
            }

            if report.errors.is_empty() {
                0
            } else {
                1
            }
        }
        Err(e) => {
            eprintln!("{e}");
            2
        }
    }
}

fn build_status_report() -> Result<(StatusReport, std::path::PathBuf), String> {
    let generated_at = Utc::now();
    let version = env!("CARGO_PKG_VERSION").to_string();
    let home = dirs::home_dir().ok_or_else(|| "Cannot determine home directory".to_string())?;

    let mut warnings: Vec<String> = Vec::new();
    let errors: Vec<String> = Vec::new();

    let setup_report = {
        let path = crate::core::setup_report::SetupReport::default_path()?;
        if path.exists() {
            match std::fs::read_to_string(&path) {
                Ok(s) => match serde_json::from_str::<crate::core::setup_report::SetupReport>(&s) {
                    Ok(r) => Some(r),
                    Err(e) => {
                        warnings.push(format!("setup report parse error: {e}"));
                        None
                    }
                },
                Err(e) => {
                    warnings.push(format!("setup report read error: {e}"));
                    None
                }
            }
        } else {
            None
        }
    };

    let (doctor_compact_passed, doctor_compact_total) = crate::doctor::compact_score();

    // MCP targets (registry based)
    let targets = crate::core::editor_registry::build_targets(&home);
    let mut mcp_targets: Vec<McpTargetStatus> = Vec::new();
    for t in &targets {
        let detected = t.detect_path.exists();
        let config_path = t.config_path.to_string_lossy().to_string();

        let state = if !detected {
            "not_detected".to_string()
        } else if !t.config_path.exists() {
            "missing_file".to_string()
        } else {
            match std::fs::read_to_string(&t.config_path) {
                Ok(s) => {
                    if s.contains("nebu-ctx") || s.contains("lean-ctx") {
                        "configured".to_string()
                    } else {
                        "missing_entry".to_string()
                    }
                }
                Err(e) => {
                    warnings.push(format!("mcp config read error for {}: {e}", t.name));
                    "read_error".to_string()
                }
            }
        };

        let note = if detected && t.agent_key == "opencode" {
            let note = opencode_support_note(&home);
            if note.starts_with("missing ") {
                warnings.push(
                    "OpenCode support files incomplete — run `nebu-ctx setup --agent opencode`"
                        .to_string(),
                );
            }
            Some(note)
        } else {
            None
        };

        if detected {
            mcp_targets.push(McpTargetStatus {
                name: t.name.to_string(),
                detected,
                config_path,
                state,
                note,
            });
        }
    }

    if mcp_targets.is_empty() {
        warnings.push("no supported AI tools detected".to_string());
    }

    let rules_targets = crate::rules_inject::collect_rules_status(&home);

    let host_connection =
        crate::config::load_connection()
            .ok()
            .flatten()
            .map(|conn| CloudConnectionStatus {
                endpoint: conn.endpoint,
                saved: true,
            });

    let sync_outbox = build_sync_outbox_status(&mut warnings);

    let path = crate::core::setup_report::status_report_path()?;

    let report = StatusReport {
        schema_version: 1,
        generated_at,
        version,
        setup_report,
        doctor_compact_passed,
        doctor_compact_total,
        mcp_targets,
        rules_targets,
        cloud_connection: host_connection,
        sync_outbox,
        warnings,
        errors,
    };

    Ok((report, path))
}

fn opencode_support_note(home: &std::path::Path) -> String {
    let mut missing = Vec::new();
    if !crate::core::editor_registry::opencode_rules_path(home).exists() {
        missing.push("rules");
    }
    if !crate::core::editor_registry::opencode_plugin_path(home).exists() {
        missing.push("plugin");
    }
    if !crate::core::editor_registry::opencode_skill_path(home).exists() {
        missing.push("skill");
    }

    if missing.is_empty() {
        "rules + plugin + skill installed".to_string()
    } else {
        format!("missing {}", missing.join(" + "))
    }
}

fn print_human(report: &StatusReport, path: &std::path::Path) {
    println!("nebu-ctx status  v{}", report.version);
    println!(
        "  doctor: {}/{}",
        report.doctor_compact_passed, report.doctor_compact_total
    );

    if let Some(setup) = &report.setup_report {
        println!(
            "  last setup: {}  success={}",
            setup.finished_at.to_rfc3339(),
            setup.success
        );
    } else if report.doctor_compact_passed == report.doctor_compact_total {
        println!("  last setup: (manual install — all checks pass)");
    } else {
        println!("  last setup: (none) — run \x1b[1mnebu-ctx setup\x1b[0m to configure");
    }

    let detected = report.mcp_targets.len();
    let configured = report
        .mcp_targets
        .iter()
        .filter(|t| t.state == "configured")
        .count();
    println!("  mcp: {configured}/{detected} configured (detected tools)");

    let rules_detected = report.rules_targets.iter().filter(|t| t.detected).count();
    let rules_up_to_date = report
        .rules_targets
        .iter()
        .filter(|t| t.detected && t.state == "up_to_date")
        .count();
    println!("  rules: {rules_up_to_date}/{rules_detected} up-to-date (detected tools)");

    if let Some(host) = &report.cloud_connection {
        println!("  host: connected → {}", host.endpoint);
    } else {
        println!("  host: not configured (run: nebu-ctx connect)");
    }

    if report.sync_outbox.readable {
        println!(
            "  sync outbox: {} queued, {} failed",
            report.sync_outbox.queued, report.sync_outbox.failed
        );
    } else {
        println!("  sync outbox: unreadable ({})", report.sync_outbox.path);
    }

    if !report.warnings.is_empty() {
        println!("  warnings: {}", report.warnings.len());
    }
    if !report.errors.is_empty() {
        println!("  errors: {}", report.errors.len());
    }
    println!("  report saved: {}", path.display());
}

/// Print a compact single-line colored status for shell startup.
/// Called via `nebu-ctx on-brief` from the shell hook's `nebu-ctx-on` function.
pub fn print_on_brief() {
    use std::io::IsTerminal;
    if !std::io::stdout().is_terminal() {
        return;
    }

    let version = env!("CARGO_PKG_VERSION");
    let host_part = match crate::config::load_connection() {
        Ok(Some(conn)) => {
            let host = conn
                .endpoint
                .trim_end_matches('/')
                .trim_start_matches("https://")
                .trim_start_matches("http://");
            format!("  \x1b[2m·\x1b[0m  \x1b[36mhost\x1b[0m \x1b[2m→ {host}\x1b[0m")
        }
        _ => String::new(),
    };

    println!(
        "  \x1b[36m◈\x1b[0m \x1b[1mnebu-ctx\x1b[0m \x1b[2mv{version}\x1b[0m  \x1b[2m·\x1b[0m  \x1b[32mON\x1b[0m{host_part}"
    );
}

fn build_sync_outbox_status(warnings: &mut Vec<String>) -> crate::sync_cli::SyncOutboxStatus {
    let status = crate::sync_cli::build_outbox_status();
    if !status.readable {
        warnings.push("sync outbox read error".to_string());
    }
    status
}
