use serde::Serialize;

use crate::Config;
use crate::cli::render::Theme;
use crate::cli::robot::RobotMeta;
use crate::core::cron::{
    CronInstallResult, CronStatusResult, CronUninstallResult, cron_status, install_cron,
    uninstall_cron,
};
use crate::core::db::create_connection;
use crate::core::error::Result;
use crate::core::ollama_mgmt::{OllamaStatusBrief, ollama_status_brief};
use crate::core::paths::get_db_path;
use crate::core::time::ms_to_iso;

// ── install ──

pub fn run_cron_install(interval_minutes: u32) -> Result<CronInstallResult> {
    install_cron(interval_minutes)
}

pub fn print_cron_install(result: &CronInstallResult) {
    if result.replaced {
        println!(
            "  {} cron entry updated (was already installed)",
            Theme::success().render("Updated")
        );
    } else {
        println!(
            "  {} cron entry installed",
            Theme::success().render("Installed")
        );
    }
    println!();
    println!("  {} {}", Theme::dim().render("entry:"), result.entry);
    println!(
        "  {} every {} minutes",
        Theme::dim().render("interval:"),
        result.interval_minutes
    );
    println!(
        "  {} {}",
        Theme::dim().render("log:"),
        result.log_path.display()
    );

    if cfg!(target_os = "macos") {
        println!();
        println!(
            "  {} On macOS, the terminal running cron may need",
            Theme::warning().render("Note:")
        );
        println!("        Full Disk Access in System Settings > Privacy & Security.");
    }
    println!();
}

#[derive(Serialize)]
struct CronInstallJson {
    ok: bool,
    data: CronInstallData,
    meta: RobotMeta,
}

#[derive(Serialize)]
struct CronInstallData {
    action: &'static str,
    entry: String,
    interval_minutes: u32,
    log_path: String,
    replaced: bool,
}

pub fn print_cron_install_json(result: &CronInstallResult, elapsed_ms: u64) {
    let output = CronInstallJson {
        ok: true,
        data: CronInstallData {
            action: "install",
            entry: result.entry.clone(),
            interval_minutes: result.interval_minutes,
            log_path: result.log_path.display().to_string(),
            replaced: result.replaced,
        },
        meta: RobotMeta::new(elapsed_ms),
    };
    if let Ok(json) = serde_json::to_string(&output) {
        println!("{json}");
    }
}

// ── uninstall ──

pub fn run_cron_uninstall() -> Result<CronUninstallResult> {
    uninstall_cron()
}

pub fn print_cron_uninstall(result: &CronUninstallResult) {
    if result.was_installed {
        println!(
            "  {} cron entry removed",
            Theme::success().render("Removed")
        );
    } else {
        println!(
            "  {} no lore-sync cron entry found",
            Theme::dim().render("Nothing to remove:")
        );
    }
    println!();
}

#[derive(Serialize)]
struct CronUninstallJson {
    ok: bool,
    data: CronUninstallData,
    meta: RobotMeta,
}

#[derive(Serialize)]
struct CronUninstallData {
    action: &'static str,
    was_installed: bool,
}

pub fn print_cron_uninstall_json(result: &CronUninstallResult, elapsed_ms: u64) {
    let output = CronUninstallJson {
        ok: true,
        data: CronUninstallData {
            action: "uninstall",
            was_installed: result.was_installed,
        },
        meta: RobotMeta::new(elapsed_ms),
    };
    if let Ok(json) = serde_json::to_string(&output) {
        println!("{json}");
    }
}

// ── status ──

pub fn run_cron_status(config: &Config) -> Result<CronStatusInfo> {
    let status = cron_status()?;

    // Query last sync run from DB
    let last_sync = get_last_sync_time(config).unwrap_or_default();

    // Quick ollama health check
    let ollama = ollama_status_brief(&config.embedding.base_url);

    Ok(CronStatusInfo {
        status,
        last_sync,
        ollama,
    })
}

pub struct CronStatusInfo {
    pub status: CronStatusResult,
    pub last_sync: Option<LastSyncInfo>,
    pub ollama: OllamaStatusBrief,
}

pub struct LastSyncInfo {
    pub started_at_iso: String,
    pub status: String,
}

fn get_last_sync_time(config: &Config) -> Result<Option<LastSyncInfo>> {
    let db_path = get_db_path(config.storage.db_path.as_deref());
    if !db_path.exists() {
        return Ok(None);
    }
    let conn = create_connection(&db_path)?;
    let result = conn.query_row(
        "SELECT started_at, status FROM sync_runs ORDER BY started_at DESC LIMIT 1",
        [],
        |row| {
            let started_at: i64 = row.get(0)?;
            let status: String = row.get(1)?;
            Ok(LastSyncInfo {
                started_at_iso: ms_to_iso(started_at),
                status,
            })
        },
    );
    match result {
        Ok(info) => Ok(Some(info)),
        Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
        // Table may not exist if migrations haven't run yet
        Err(rusqlite::Error::SqliteFailure(_, Some(ref msg))) if msg.contains("no such table") => {
            Ok(None)
        }
        Err(e) => Err(e.into()),
    }
}

pub fn print_cron_status(info: &CronStatusInfo) {
    if info.status.installed {
        println!(
            "  {} lore-sync is installed in crontab",
            Theme::success().render("Installed")
        );
        if let Some(interval) = info.status.interval_minutes {
            println!(
                "  {} every {} minutes",
                Theme::dim().render("interval:"),
                interval
            );
        }
        if let Some(ref binary) = info.status.binary_path {
            let label = if info.status.binary_mismatch {
                Theme::warning().render("binary:")
            } else {
                Theme::dim().render("binary:")
            };
            println!("  {label} {binary}");
            if info.status.binary_mismatch
                && let Some(ref current) = info.status.current_binary
            {
                println!(
                    "  {}",
                    Theme::warning().render(&format!("  current binary is {current} (mismatch!)"))
                );
            }
        }
        if let Some(ref log) = info.status.log_path {
            println!("  {} {}", Theme::dim().render("log:"), log.display());
        }
    } else {
        println!(
            "  {} lore-sync is not installed in crontab",
            Theme::dim().render("Not installed:")
        );
        println!(
            "  {} lore cron install",
            Theme::dim().render("install with:")
        );
    }

    if let Some(ref last) = info.last_sync {
        println!(
            "  {} {} ({})",
            Theme::dim().render("last sync:"),
            last.started_at_iso,
            last.status
        );
    }

    // Ollama status
    if info.ollama.installed {
        if info.ollama.running {
            println!(
                "  {} running (auto-started by cron if needed)",
                Theme::dim().render("ollama:")
            );
        } else {
            println!(
                "  {} {}",
                Theme::warning().render("ollama:"),
                Theme::warning()
                    .render("installed but not running (will attempt start on next sync)")
            );
        }
    } else {
        println!(
            "  {} {}",
            Theme::error().render("ollama:"),
            Theme::error().render("not installed — embeddings unavailable")
        );
        if let Some(ref hint) = info.ollama.install_hint {
            println!("            {hint}");
        }
    }
    println!();
}

#[derive(Serialize)]
struct CronStatusJson {
    ok: bool,
    data: CronStatusData,
    meta: RobotMeta,
}

#[derive(Serialize)]
struct CronStatusData {
    installed: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    interval_minutes: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    binary_path: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    current_binary: Option<String>,
    binary_mismatch: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    log_path: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    cron_entry: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    last_sync_at: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    last_sync_status: Option<String>,
    ollama: OllamaStatusBrief,
}

pub fn print_cron_status_json(info: &CronStatusInfo, elapsed_ms: u64) {
    let output = CronStatusJson {
        ok: true,
        data: CronStatusData {
            installed: info.status.installed,
            interval_minutes: info.status.interval_minutes,
            binary_path: info.status.binary_path.clone(),
            current_binary: info.status.current_binary.clone(),
            binary_mismatch: info.status.binary_mismatch,
            log_path: info
                .status
                .log_path
                .as_ref()
                .map(|p| p.display().to_string()),
            cron_entry: info.status.cron_entry.clone(),
            last_sync_at: info.last_sync.as_ref().map(|s| s.started_at_iso.clone()),
            last_sync_status: info.last_sync.as_ref().map(|s| s.status.clone()),
            ollama: info.ollama.clone(),
        },
        meta: RobotMeta::new(elapsed_ms),
    };
    if let Ok(json) = serde_json::to_string(&output) {
        println!("{json}");
    }
}
