//! `fxt service` -- service operations.

use anyhow::Result;
use clap::{Parser, Subcommand};
use colored::Colorize;
use serde::Serialize;

use crate::grpc::FoxhuntClient;
use crate::output::{self, HumanReadable, OutputFormat};
use crate::proto::monitoring;

#[derive(Parser, Debug)]
pub struct ServiceCommand {
    #[command(subcommand)]
    action: ServiceAction,
}

#[derive(Subcommand, Debug)]
enum ServiceAction {
    /// List all services with health status
    List,
    /// Detailed service status
    Status {
        /// Service name
        service: String,
    },
    /// Stream service logs
    Logs {
        /// Service name
        service: String,
        /// Follow log output
        #[arg(long, short)]
        follow: bool,
    },
    /// Restart a service
    Restart {
        /// Service name
        service: String,
    },
    /// Deploy binary update
    Deploy {
        /// Service name
        service: String,
    },
    /// Health check all services
    Health,
}

// ── Result types ──────────────────────────────────────────────────────

#[derive(Serialize)]
struct ServiceListResult {
    overall_health: String,
    healthy: i32,
    total: i32,
    uptime_seconds: i64,
    services: Vec<ServiceEntry>,
}

#[derive(Serialize)]
struct ServiceEntry {
    name: String,
    health: String,
    state: String,
    version: String,
    uptime_seconds: i64,
    error: String,
}

#[derive(Serialize)]
struct ServiceStatusResult {
    name: String,
    health: String,
    state: String,
    version: String,
    uptime_seconds: i64,
    error: String,
    metadata: Vec<(String, String)>,
    dependencies: Vec<DependencyEntry>,
}

#[derive(Serialize)]
struct DependencyEntry {
    name: String,
    dep_type: String,
    status: String,
    endpoint: String,
    response_time_ms: f64,
}

#[derive(Serialize)]
struct HealthCheckResult {
    overall: String,
    checks: Vec<HealthEntry>,
}

#[derive(Serialize)]
struct HealthEntry {
    name: String,
    status: String,
    message: String,
    response_time_ms: f64,
}

// ── Display helpers ──────────────────────────────────────────────────

fn service_health_str(h: i32) -> &'static str {
    use crate::proto::monitoring::ServiceHealth;
    match ServiceHealth::try_from(h) {
        Ok(ServiceHealth::Healthy) => "healthy",
        Ok(ServiceHealth::Degraded) => "degraded",
        Ok(ServiceHealth::Unhealthy) => "unhealthy",
        Ok(ServiceHealth::Critical) => "critical",
        _ => "unknown",
    }
}

fn service_state_str(s: i32) -> &'static str {
    use crate::proto::monitoring::ServiceState;
    match ServiceState::try_from(s) {
        Ok(ServiceState::Starting) => "starting",
        Ok(ServiceState::Running) => "running",
        Ok(ServiceState::Stopping) => "stopping",
        Ok(ServiceState::Stopped) => "stopped",
        Ok(ServiceState::Error) => "error",
        _ => "unknown",
    }
}

fn system_health_str(h: i32) -> &'static str {
    use crate::proto::monitoring::SystemHealth;
    match SystemHealth::try_from(h) {
        Ok(SystemHealth::Healthy) => "healthy",
        Ok(SystemHealth::Degraded) => "degraded",
        Ok(SystemHealth::Unhealthy) => "unhealthy",
        Ok(SystemHealth::Critical) => "critical",
        _ => "unknown",
    }
}

fn health_status_str(h: i32) -> &'static str {
    use crate::proto::monitoring::HealthStatus;
    match HealthStatus::try_from(h) {
        Ok(HealthStatus::Healthy) => "healthy",
        Ok(HealthStatus::Degraded) => "degraded",
        Ok(HealthStatus::Unhealthy) => "unhealthy",
        Ok(HealthStatus::Critical) => "critical",
        _ => "unknown",
    }
}

fn dependency_type_str(t: i32) -> &'static str {
    use crate::proto::monitoring::DependencyType;
    match DependencyType::try_from(t) {
        Ok(DependencyType::Database) => "database",
        Ok(DependencyType::MessageQueue) => "message_queue",
        Ok(DependencyType::Cache) => "cache",
        Ok(DependencyType::ExternalApi) => "external_api",
        Ok(DependencyType::FileSystem) => "file_system",
        Ok(DependencyType::Network) => "network",
        _ => "unknown",
    }
}

fn colorize_health(s: &str) -> String {
    match s {
        "healthy" => s.green().bold().to_string(),
        "degraded" => s.yellow().bold().to_string(),
        "unhealthy" => s.red().to_string(),
        "critical" => s.red().bold().to_string(),
        _ => s.dimmed().to_string(),
    }
}

fn colorize_state(s: &str) -> String {
    match s {
        "running" => s.green().to_string(),
        "starting" | "stopping" => s.yellow().to_string(),
        "stopped" => s.dimmed().to_string(),
        "error" => s.red().bold().to_string(),
        _ => s.dimmed().to_string(),
    }
}

#[allow(clippy::integer_division, clippy::modulo_arithmetic)]
fn format_uptime(secs: i64) -> String {
    if secs < 60 {
        return format!("{secs}s");
    }
    let days = secs / 86400;
    let hours = (secs % 86400) / 3600;
    let mins = (secs % 3600) / 60;
    if days > 0 {
        format!("{days}d {hours}h {mins}m")
    } else if hours > 0 {
        format!("{hours}h {mins}m")
    } else {
        format!("{mins}m")
    }
}

// ── HumanReadable impls ──────────────────────────────────────────────

impl HumanReadable for ServiceListResult {
    fn print_human(&self) {
        println!(
            "{} {} ({}/{} healthy, uptime {})\n",
            "System:".bold(),
            colorize_health(&self.overall_health),
            self.healthy,
            self.total,
            format_uptime(self.uptime_seconds).dimmed(),
        );

        println!(
            "  {:<28} {:<12} {:<12} {:<10} {}",
            "SERVICE".bold(),
            "HEALTH".bold(),
            "STATE".bold(),
            "UPTIME".bold(),
            "VERSION".bold(),
        );
        println!("  {}", "-".repeat(76));

        for svc in &self.services {
            let err_suffix = if svc.error.is_empty() {
                String::new()
            } else {
                format!("  {}", svc.error.red())
            };
            println!(
                "  {:<28} {:<12} {:<12} {:<10} {}{}",
                svc.name,
                colorize_health(&svc.health),
                colorize_state(&svc.state),
                format_uptime(svc.uptime_seconds),
                svc.version.dimmed(),
                err_suffix,
            );
        }
    }
}

impl HumanReadable for ServiceStatusResult {
    fn print_human(&self) {
        println!("{} {}", "Service:".bold(), self.name);
        println!("  Health:  {}", colorize_health(&self.health));
        println!("  State:   {}", colorize_state(&self.state));
        println!("  Version: {}", self.version);
        println!("  Uptime:  {}", format_uptime(self.uptime_seconds));

        if !self.error.is_empty() {
            println!("  Error:   {}", self.error.red());
        }

        if !self.metadata.is_empty() {
            println!("\n  {}:", "Metadata".bold());
            for (k, v) in &self.metadata {
                println!("    {k}: {v}");
            }
        }

        if !self.dependencies.is_empty() {
            println!("\n  {}:", "Dependencies".bold());
            for dep in &self.dependencies {
                let status_col = colorize_health(&dep.status);
                let ep = if dep.endpoint.is_empty() {
                    String::new()
                } else {
                    format!(" ({})", dep.endpoint.dimmed())
                };
                println!(
                    "    {} [{}] {} {:.1}ms{}",
                    dep.name, dep.dep_type, status_col, dep.response_time_ms, ep,
                );
            }
        }
    }
}

impl HumanReadable for HealthCheckResult {
    fn print_human(&self) {
        println!("{} {}\n", "Overall:".bold(), colorize_health(&self.overall));

        println!(
            "  {:<32} {:<12} {:>10}  {}",
            "CHECK".bold(),
            "STATUS".bold(),
            "LATENCY".bold(),
            "MESSAGE".bold(),
        );
        println!("  {}", "-".repeat(72));

        for c in &self.checks {
            let latency_str = if c.response_time_ms > 0.0 {
                format!("{:.1}ms", c.response_time_ms)
            } else {
                "-".to_owned()
            };
            println!(
                "  {:<32} {:<12} {:>10}  {}",
                c.name,
                colorize_health(&c.status),
                latency_str,
                c.message.dimmed(),
            );
        }
    }
}

// ── Stub result for non-gRPC commands ────────────────────────────────

#[derive(Serialize)]
struct StubMessage {
    message: String,
}

impl HumanReadable for StubMessage {
    fn print_human(&self) {
        println!("{}", self.message);
    }
}

// ── Execute ──────────────────────────────────────────────────────────

impl ServiceCommand {
    pub async fn execute(&self, client: &FoxhuntClient, format: OutputFormat) -> Result<()> {
        match &self.action {
            ServiceAction::List => {
                let resp = client
                    .monitoring()
                    .get_system_status(monitoring::GetSystemStatusRequest {
                        service_names: vec![],
                    })
                    .await?
                    .into_inner();

                let overall = resp.overall_status.unwrap_or_default();
                let services: Vec<ServiceEntry> = resp
                    .service_statuses
                    .iter()
                    .map(|s| ServiceEntry {
                        name: s.service_name.clone(),
                        health: service_health_str(s.health).to_owned(),
                        state: service_state_str(s.state).to_owned(),
                        version: s.version.clone().unwrap_or_default(),
                        uptime_seconds: s.uptime_seconds,
                        error: s.error_message.clone().unwrap_or_default(),
                    })
                    .collect();

                let result = ServiceListResult {
                    overall_health: system_health_str(overall.overall_health).to_owned(),
                    healthy: overall.healthy_services,
                    total: overall.total_services,
                    uptime_seconds: overall.system_uptime_seconds,
                    services,
                };
                output::render(&result, format)?;
            }
            ServiceAction::Status { service } => {
                let resp = client
                    .monitoring()
                    .get_system_status(monitoring::GetSystemStatusRequest {
                        service_names: vec![service.clone()],
                    })
                    .await?
                    .into_inner();

                let maybe_svc = resp
                    .service_statuses
                    .into_iter()
                    .find(|s| s.service_name == *service);

                let Some(svc) = maybe_svc else {
                    anyhow::bail!("service '{}' not found in system status", service);
                };

                let metadata: Vec<(String, String)> =
                    svc.metadata.into_iter().collect();

                let dependencies: Vec<DependencyEntry> = svc
                    .dependencies
                    .iter()
                    .map(|d| DependencyEntry {
                        name: d.name.clone(),
                        dep_type: dependency_type_str(d.dependency_type).to_owned(),
                        status: health_status_str(d.status).to_owned(),
                        endpoint: d.endpoint.clone().unwrap_or_default(),
                        response_time_ms: d.response_time_ms.unwrap_or_default(),
                    })
                    .collect();

                let result = ServiceStatusResult {
                    name: svc.service_name,
                    health: service_health_str(svc.health).to_owned(),
                    state: service_state_str(svc.state).to_owned(),
                    version: svc.version.unwrap_or_default(),
                    uptime_seconds: svc.uptime_seconds,
                    error: svc.error_message.unwrap_or_default(),
                    metadata,
                    dependencies,
                };
                output::render(&result, format)?;
            }
            ServiceAction::Health => {
                let resp = client
                    .monitoring()
                    .get_health_check(monitoring::GetHealthCheckRequest {
                        service_name: None,
                    })
                    .await?
                    .into_inner();

                let checks: Vec<HealthEntry> = resp
                    .health_checks
                    .iter()
                    .map(|c| HealthEntry {
                        name: c.check_name.clone(),
                        status: health_status_str(c.status).to_owned(),
                        message: c.message.clone().unwrap_or_default(),
                        response_time_ms: c.response_time_ms.unwrap_or_default(),
                    })
                    .collect();

                let result = HealthCheckResult {
                    overall: health_status_str(resp.health_status).to_owned(),
                    checks,
                };
                output::render(&result, format)?;
            }
            ServiceAction::Logs { service, follow: _ } => {
                let result = StubMessage {
                    message: format!(
                        "Service logs are available via kubectl:\n  \
                         kubectl logs -f deploy/{service} -n foxhunt"
                    ),
                };
                output::render(&result, format)?;
            }
            ServiceAction::Restart { service } => {
                let result = StubMessage {
                    message: format!(
                        "Service restart requires kubectl access:\n  \
                         kubectl rollout restart deploy/{service} -n foxhunt"
                    ),
                };
                output::render(&result, format)?;
            }
            ServiceAction::Deploy { service } => {
                let result = StubMessage {
                    message: format!(
                        "Use pod-writer-deploy for deployment:\n  \
                         kubectl apply -f infra/k8s/services/{service}.yaml && \
                         kubectl rollout restart deploy/{service} -n foxhunt"
                    ),
                };
                output::render(&result, format)?;
            }
        }
        Ok(())
    }
}
