use std::fs;
use std::net::UdpSocket;
use std::path::{Path, PathBuf};
use std::process::{Child, Command, Stdio};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};

use core_api::{ServiceRequestId, UdpServiceClient};
use serde::Serialize;
use serde_json::Value;

use crate::args::{has_flag, option_value};
use crate::gateway::{
    GATEWAY_PLANE_GOVERNANCE, GATEWAY_ROUTE_MODE_DIRECT_OR_NEARBY, STATUS_OP_GATEWAY_SHUTDOWN,
    STATUS_OP_SNAPSHOT, STATUS_SERVICE_API_VERSION, STATUS_SERVICE_NAME, GatewayRequestMeta,
    StatusServiceRequest, StatusServiceResponse,
};
use crate::serve::serve_gateway_with_shutdown;

const DEFAULT_SIDECAR_EVIDENCE_PATH: &str = "artifacts/sidecar/gateway-sidecar-evidence.json";
const DEFAULT_SIDECAR_RUNTIME_PATH: &str = "artifacts/sidecar/gateway-sidecar-runtime.json";

#[derive(Clone, Debug)]
enum SidecarMode {
    Embedded,
    Standalone,
}

#[derive(Clone, Debug)]
struct SidecarConfig {
    mode: SidecarMode,
    bind: String,
    advertise_endpoint: String,
    source: String,
    status_report: String,
    runtime_report: String,
    middleware_report: String,
    resource_report: String,
    node_id: String,
    tenant: String,
    namespace: String,
    sampling_ms: u64,
    log_level: String,
    audit_enabled: bool,
    health_timeout_ms: u64,
    health_interval_ms: u64,
    handshake_retries: u64,
    restart_max: u64,
    child_once: bool,
    once: bool,
    json: bool,
    evidence_path: PathBuf,
    registry_path: PathBuf,
    cluster_id: String,
    cluster_zone: String,
    cluster_peers: Vec<String>,
}

#[derive(Debug, Serialize)]
struct SidecarEvidence {
    api_version: String,
    started_at_unix_nanos: u128,
    finished_at_unix_nanos: Option<u128>,
    launcher: Value,
    process_status: String,
    restart_count: u64,
    last_error: Option<String>,
    health_checks: Vec<Value>,
    events: Vec<Value>,
}

impl SidecarEvidence {
    fn new(config: &SidecarConfig) -> Self {
        Self {
            api_version: String::from("robotrt.gateway.sidecar.evidence.v1"),
            started_at_unix_nanos: now_unix_nanos(),
            finished_at_unix_nanos: None,
            launcher: serde_json::json!({
                "mode": sidecar_mode_name(&config.mode),
                "bind": config.bind,
                "advertise_endpoint": config.advertise_endpoint,
                "source": config.source,
                "node_id": config.node_id,
                "tenant": config.tenant,
                "namespace": config.namespace,
                "cluster": {
                    "id": config.cluster_id,
                    "zone": config.cluster_zone,
                    "peers": config.cluster_peers,
                },
                "sampling_ms": config.sampling_ms,
                "log_level": config.log_level,
                "audit_enabled": config.audit_enabled,
                "health_timeout_ms": config.health_timeout_ms,
                "health_interval_ms": config.health_interval_ms,
                "handshake_retries": config.handshake_retries,
                "restart_max": config.restart_max,
                "child_once": config.child_once,
            }),
            process_status: String::from("starting"),
            restart_count: 0,
            last_error: None,
            health_checks: Vec::new(),
            events: Vec::new(),
        }
    }

    fn push_event(&mut self, category: &str, action: &str, status: &str, detail: Value) {
        self.events.push(serde_json::json!({
            "captured_at_unix_nanos": now_unix_nanos(),
            "category": category,
            "action": action,
            "status": status,
            "detail": detail,
        }));
    }

    fn push_health(&mut self, ok: bool, detail: Value) {
        self.health_checks.push(serde_json::json!({
            "captured_at_unix_nanos": now_unix_nanos(),
            "ok": ok,
            "detail": detail,
        }));
    }
}

pub fn run_sidecar(args: &[String]) -> Result<(), String> {
    let config = parse_config(args)?;
    let mut evidence = SidecarEvidence::new(&config);

    if config.json {
        println!(
            "{}",
            serde_json::to_string_pretty(&serde_json::json!({
                "api_version": "robotrt.gateway.sidecar.launch.v1",
                "mode": sidecar_mode_name(&config.mode),
                "bind": config.bind,
                "advertise_endpoint": config.advertise_endpoint,
                "source": config.source,
                "evidence": config.evidence_path,
                "registry": config.registry_path,
                "node_id": config.node_id,
                "tenant": config.tenant,
                "namespace": config.namespace,
                "cluster": {
                    "id": config.cluster_id,
                    "zone": config.cluster_zone,
                    "peers": config.cluster_peers,
                },
                "health": {
                    "timeout_ms": config.health_timeout_ms,
                    "interval_ms": config.health_interval_ms,
                    "handshake_retries": config.handshake_retries,
                },
                "restart_max": config.restart_max,
            }))
            .map_err(|err| format!("serialize sidecar bootstrap failed: {err}"))?
        );
    } else {
        println!("RobotRT Gateway Sidecar");
        println!("mode: {}", sidecar_mode_name(&config.mode));
        println!("bind: {}", config.bind);
        println!("advertise_endpoint: {}", config.advertise_endpoint);
        println!("source: {}", config.source);
        println!("node_id: {}", config.node_id);
        println!("tenant: {}", config.tenant);
        println!("namespace: {}", config.namespace);
        println!("evidence: {}", config.evidence_path.display());
        println!("registry: {}", config.registry_path.display());
        println!("cluster_id: {}", config.cluster_id);
        println!("cluster_zone: {}", config.cluster_zone);
        if !config.cluster_peers.is_empty() {
            println!("cluster_peers: {}", config.cluster_peers.join(","));
        }
    }

    if let Err(err) = preflight(&config) {
        evidence.process_status = String::from("failed_preflight");
        evidence.last_error = Some(err.clone());
        evidence.push_event("lifecycle", "preflight", "error", serde_json::json!({"error": err}));
        let _ = write_evidence(&config.evidence_path, &evidence);
        return Err(err);
    }

    let shutdown = Arc::new(AtomicBool::new(false));
    install_signal_handler(shutdown.clone())?;
    evidence.push_event("lifecycle", "preflight", "ok", serde_json::json!({"bind": config.bind}));
    let _ = write_runtime_registry(&config, "starting", 0, None);

    let result = match config.mode {
        SidecarMode::Embedded => run_embedded(args, &config, shutdown.clone(), &mut evidence),
        SidecarMode::Standalone => run_standalone(&config, shutdown.clone(), &mut evidence),
    };

    if let Err(err) = &result {
        evidence.process_status = String::from("failed");
        evidence.last_error = Some(err.clone());
        evidence.push_event("lifecycle", "sidecar_exit", "error", serde_json::json!({"error": err}));
    } else {
        evidence.process_status = String::from("stopped");
        evidence.push_event("lifecycle", "sidecar_exit", "ok", serde_json::json!({}));
    }
    evidence.finished_at_unix_nanos = Some(now_unix_nanos());
    write_evidence(&config.evidence_path, &evidence)?;
    let _ = write_runtime_registry(
        &config,
        &evidence.process_status,
        evidence.restart_count,
        evidence.last_error.as_deref(),
    );
    result
}

fn run_embedded(
    args: &[String],
    config: &SidecarConfig,
    shutdown: Arc<AtomicBool>,
    evidence: &mut SidecarEvidence,
) -> Result<(), String> {
    evidence.push_event(
        "lifecycle",
        "embedded_start",
        "ok",
        serde_json::json!({"bind": config.bind}),
    );

    let server_args = args.to_vec();
    let server_shutdown = shutdown.clone();
    let handle = thread::spawn(move || serve_gateway_with_shutdown(&server_args, Some(server_shutdown)));

    wait_handshake(config, evidence)?;
    let mut last_probe = Instant::now();

    loop {
        if shutdown.load(Ordering::Relaxed) {
            break;
        }

        if handle.is_finished() {
            break;
        }

        if last_probe.elapsed() >= Duration::from_millis(config.health_interval_ms) {
            match health_probe(&config.bind, config.health_timeout_ms) {
                Ok(summary) => evidence.push_health(true, summary),
                Err(err) => {
                    evidence.push_health(false, serde_json::json!({"error": err}));
                    shutdown.store(true, Ordering::Relaxed);
                    break;
                }
            }
            last_probe = Instant::now();
        }

        thread::sleep(Duration::from_millis(50));
    }

    shutdown.store(true, Ordering::Relaxed);
    match handle.join() {
        Ok(result) => result,
        Err(_) => Err(String::from("embedded sidecar server thread panicked")),
    }
}

fn run_standalone(
    config: &SidecarConfig,
    shutdown: Arc<AtomicBool>,
    evidence: &mut SidecarEvidence,
) -> Result<(), String> {
    let mut restart_count = 0u64;

    loop {
        if shutdown.load(Ordering::Relaxed) {
            evidence.restart_count = restart_count;
            return Ok(());
        }

        let mut child = spawn_gateway_child(config)?;
        evidence.push_event(
            "lifecycle",
            "child_spawn",
            "ok",
            serde_json::json!({"pid": child.id()}),
        );

        if let Err(err) = wait_handshake(config, evidence) {
            graceful_stop_child(&mut child, config);
            if restart_count >= config.restart_max {
                evidence.restart_count = restart_count;
                return Err(format!(
                    "sidecar handshake failed and restart budget exhausted: {err}"
                ));
            }
            restart_count = restart_count.saturating_add(1);
            evidence.restart_count = restart_count;
            evidence.push_event(
                "lifecycle",
                "handshake_restart",
                "ok",
                serde_json::json!({"restart_count": restart_count, "error": err}),
            );
            continue;
        }

        if config.once {
            send_shutdown(&config.bind, config.health_timeout_ms);
            let _ = wait_child_exit(&mut child, Duration::from_secs(2));
            evidence.restart_count = restart_count;
            return Ok(());
        }

        let mut last_probe = Instant::now();
        loop {
            if shutdown.load(Ordering::Relaxed) {
                graceful_stop_child(&mut child, config);
                evidence.restart_count = restart_count;
                return Ok(());
            }

            if let Some(status) = child
                .try_wait()
                .map_err(|err| format!("check sidecar child exit failed: {err}"))?
            {
                if status.success() {
                    evidence.push_event(
                        "lifecycle",
                        "child_exit",
                        "warning",
                        serde_json::json!({"status": status.code()}),
                    );
                } else {
                    evidence.push_event(
                        "lifecycle",
                        "child_exit",
                        "error",
                        serde_json::json!({"status": status.code()}),
                    );
                }

                if restart_count >= config.restart_max {
                    evidence.restart_count = restart_count;
                    return Err(format!(
                        "sidecar child exited and restart budget exhausted (restart_max={})",
                        config.restart_max
                    ));
                }

                restart_count = restart_count.saturating_add(1);
                evidence.restart_count = restart_count;
                evidence.push_event(
                    "lifecycle",
                    "child_restart",
                    "ok",
                    serde_json::json!({"restart_count": restart_count}),
                );
                break;
            }

            if last_probe.elapsed() >= Duration::from_millis(config.health_interval_ms) {
                match health_probe(&config.bind, config.health_timeout_ms) {
                    Ok(summary) => evidence.push_health(true, summary),
                    Err(err) => {
                        evidence.push_health(false, serde_json::json!({"error": err}));
                        graceful_stop_child(&mut child, config);

                        if restart_count >= config.restart_max {
                            evidence.restart_count = restart_count;
                            return Err(format!(
                                "sidecar health probe failed and restart budget exhausted (restart_max={})",
                                config.restart_max
                            ));
                        }

                        restart_count = restart_count.saturating_add(1);
                        evidence.restart_count = restart_count;
                        evidence.push_event(
                            "lifecycle",
                            "health_restart",
                            "ok",
                            serde_json::json!({"restart_count": restart_count}),
                        );
                        break;
                    }
                }
                last_probe = Instant::now();
            }

            thread::sleep(Duration::from_millis(50));
        }
    }
}

fn parse_config(args: &[String]) -> Result<SidecarConfig, String> {
    let mode = match option_value(args, "--mode")
        .unwrap_or_else(|| String::from("embedded"))
        .as_str()
    {
        "embedded" => SidecarMode::Embedded,
        "standalone" => SidecarMode::Standalone,
        other => {
            return Err(format!(
                "unsupported --mode value: {other} (expected embedded|standalone)"
            ));
        }
    };

    let audit_enabled = option_value(args, "--audit-enabled")
        .map(|value| parse_bool(&value))
        .transpose()?
        .unwrap_or(true);

    let bind = option_value(args, "--bind").unwrap_or_else(|| String::from("127.0.0.1:7588"));
    let advertise_endpoint = option_value(args, "--advertise-endpoint")
        .filter(|value| !value.trim().is_empty())
        .unwrap_or_else(|| bind.clone());

    Ok(SidecarConfig {
        mode,
        bind,
        advertise_endpoint,
        source: option_value(args, "--source").unwrap_or_else(|| String::from("live")),
        status_report: option_value(args, "--report")
            .unwrap_or_else(|| String::from("artifacts/introspection/local-loop-status.json")),
        runtime_report: option_value(args, "--runtime-report")
            .unwrap_or_else(|| String::from("artifacts/introspection/local-loop-runtime.json")),
        middleware_report: option_value(args, "--middleware-report")
            .unwrap_or_else(|| String::from("artifacts/introspection/local-loop-load.json")),
        resource_report: option_value(args, "--resource-report")
            .unwrap_or_else(|| String::from("artifacts/introspection/local-loop-resources.json")),
        node_id: option_value(args, "--node-id").unwrap_or_else(|| String::from("gateway-sidecar")),
        tenant: option_value(args, "--tenant").unwrap_or_else(|| String::from("default")),
        namespace: option_value(args, "--namespace").unwrap_or_else(|| String::from("default")),
        sampling_ms: parse_u64_option(args, "--sampling-ms", 1000)?,
        log_level: option_value(args, "--log-level").unwrap_or_else(|| String::from("info")),
        audit_enabled,
        health_timeout_ms: parse_u64_option(args, "--health-timeout-ms", 1200)?,
        health_interval_ms: parse_u64_option(args, "--health-interval-ms", 500)?,
        handshake_retries: parse_u64_option(args, "--handshake-retries", 20)?,
        restart_max: parse_u64_option(args, "--restart-max", 3)?,
        child_once: has_flag(args, "--child-once"),
        once: has_flag(args, "--once"),
        json: has_flag(args, "--json"),
        evidence_path: PathBuf::from(
            option_value(args, "--evidence")
                .unwrap_or_else(|| String::from(DEFAULT_SIDECAR_EVIDENCE_PATH)),
        ),
        registry_path: PathBuf::from(
            option_value(args, "--registry")
                .unwrap_or_else(|| String::from(DEFAULT_SIDECAR_RUNTIME_PATH)),
        ),
        cluster_id: option_value(args, "--cluster-id")
            .unwrap_or_else(|| String::from("default-cluster")),
        cluster_zone: option_value(args, "--cluster-zone")
            .unwrap_or_else(|| String::from("default-zone")),
        cluster_peers: option_value(args, "--cluster-peers")
            .map(|raw| parse_csv_list(&raw))
            .unwrap_or_default(),
    })
}

fn preflight(config: &SidecarConfig) -> Result<(), String> {
    if config.source != "demo" && config.source != "file" && config.source != "live" {
        return Err(format!(
            "unsupported --source value: {} (expected live|demo|file)",
            config.source
        ));
    }

    UdpSocket::bind(&config.bind)
        .map_err(|err| format!("sidecar preflight bind check failed for {}: {err}", config.bind))?;
    Ok(())
}

fn install_signal_handler(shutdown: Arc<AtomicBool>) -> Result<(), String> {
    ctrlc::set_handler(move || {
        shutdown.store(true, Ordering::Relaxed);
    })
    .map_err(|err| format!("install sidecar signal handler failed: {err}"))
}

fn spawn_gateway_child(config: &SidecarConfig) -> Result<Child, String> {
    let exe = std::env::current_exe().map_err(|err| format!("resolve host executable failed: {err}"))?;

    let mut cmd = Command::new(exe);
    cmd.arg("gateway")
        .arg("--bind")
        .arg(&config.bind)
        .arg("--source")
        .arg(&config.source)
        .arg("--report")
        .arg(&config.status_report)
        .arg("--runtime-report")
        .arg(&config.runtime_report)
        .arg("--middleware-report")
        .arg(&config.middleware_report)
        .arg("--resource-report")
        .arg(&config.resource_report)
        .arg("--audit-enabled")
        .arg(if config.audit_enabled { "true" } else { "false" })
        .stdout(Stdio::null())
        .stderr(Stdio::piped());

    if config.child_once {
        cmd.arg("--once");
    }

    cmd.spawn()
        .map_err(|err| format!("spawn gateway sidecar child failed: {err}"))
}

fn wait_handshake(config: &SidecarConfig, evidence: &mut SidecarEvidence) -> Result<(), String> {
    for _ in 0..config.handshake_retries {
        match health_probe(&config.bind, config.health_timeout_ms) {
            Ok(summary) => {
                evidence.push_health(true, summary);
                evidence.push_event(
                    "lifecycle",
                    "handshake",
                    "ok",
                    serde_json::json!({"endpoint": config.bind}),
                );
                return Ok(());
            }
            Err(err) => {
                evidence.push_health(false, serde_json::json!({"error": err}));
                thread::sleep(Duration::from_millis(config.health_interval_ms.max(20)));
            }
        }
    }

    Err(format!(
        "sidecar handshake timeout after {} retries",
        config.handshake_retries
    ))
}

fn health_probe(endpoint: &str, timeout_ms: u64) -> Result<Value, String> {
    let client = UdpServiceClient::new(endpoint.to_string(), Duration::from_millis(timeout_ms))
        .map_err(|err| format!("create sidecar health client failed: {err}"))?;

    let req = StatusServiceRequest {
        api_version: STATUS_SERVICE_API_VERSION.to_string(),
        op: STATUS_OP_SNAPSHOT.to_string(),
        snapshot_config: None,
        service_call: None,
        op_payload: None,
        gateway_meta: Some(GatewayRequestMeta {
            plane: GATEWAY_PLANE_GOVERNANCE.to_string(),
            route_mode: GATEWAY_ROUTE_MODE_DIRECT_OR_NEARBY.to_string(),
            traffic_kind: String::from("governance"),
            allow_gateway_data_proxy: false,
        }),
    };

    let resp: StatusServiceResponse = client
        .call_json(STATUS_SERVICE_NAME, ServiceRequestId(9001), &req)
        .map_err(|err| format!("sidecar health probe request failed: {err}"))?;

    if resp.op != STATUS_OP_SNAPSHOT {
        return Err(format!("sidecar health probe unexpected op={}", resp.op));
    }

    let node_count = resp
        .snapshot
        .as_ref()
        .map(|snapshot| snapshot.nodes.len())
        .unwrap_or(0);

    Ok(serde_json::json!({
        "endpoint": endpoint,
        "op": resp.op,
        "node_count": node_count,
    }))
}

fn send_shutdown(endpoint: &str, timeout_ms: u64) {
    let client = match UdpServiceClient::new(endpoint.to_string(), Duration::from_millis(timeout_ms)) {
        Ok(client) => client,
        Err(_) => return,
    };

    let req = StatusServiceRequest {
        api_version: STATUS_SERVICE_API_VERSION.to_string(),
        op: STATUS_OP_GATEWAY_SHUTDOWN.to_string(),
        snapshot_config: None,
        service_call: None,
        op_payload: None,
        gateway_meta: Some(GatewayRequestMeta {
            plane: GATEWAY_PLANE_GOVERNANCE.to_string(),
            route_mode: GATEWAY_ROUTE_MODE_DIRECT_OR_NEARBY.to_string(),
            traffic_kind: String::from("governance"),
            allow_gateway_data_proxy: false,
        }),
    };

    let _ = client.call_json::<_, StatusServiceResponse>(
        STATUS_SERVICE_NAME,
        ServiceRequestId(9002),
        &req,
    );
}

fn graceful_stop_child(child: &mut Child, config: &SidecarConfig) {
    send_shutdown(&config.bind, config.health_timeout_ms);
    if wait_child_exit(child, Duration::from_secs(2)).is_err() {
        let _ = child.kill();
        let _ = child.wait();
    }
}

fn wait_child_exit(child: &mut Child, timeout: Duration) -> Result<(), String> {
    let deadline = Instant::now() + timeout;
    loop {
        if child
            .try_wait()
            .map_err(|err| format!("wait child status failed: {err}"))?
            .is_some()
        {
            return Ok(());
        }
        if Instant::now() >= deadline {
            return Err(String::from("wait child exit timeout"));
        }
        thread::sleep(Duration::from_millis(30));
    }
}

fn write_evidence(path: &Path, evidence: &SidecarEvidence) -> Result<(), String> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent).map_err(|err| {
            format!(
                "create sidecar evidence dir {} failed: {err}",
                parent.display()
            )
        })?;
    }

    let body = serde_json::to_string_pretty(evidence)
        .map_err(|err| format!("serialize sidecar evidence failed: {err}"))?;
    fs::write(path, body)
        .map_err(|err| format!("write sidecar evidence {} failed: {err}", path.display()))
}

fn write_runtime_registry(
    config: &SidecarConfig,
    process_status: &str,
    restart_count: u64,
    last_error: Option<&str>,
) -> Result<(), String> {
    if let Some(parent) = config.registry_path.parent() {
        fs::create_dir_all(parent).map_err(|err| {
            format!(
                "create sidecar runtime dir {} failed: {err}",
                parent.display()
            )
        })?;
    }

    let payload = serde_json::json!({
        "api_version": "robotrt.gateway.sidecar.runtime.v1",
        "updated_at_unix_nanos": now_unix_nanos(),
        "launcher_pid": std::process::id(),
        "mode": sidecar_mode_name(&config.mode),
        "endpoint": config.bind,
        "advertise_endpoint": config.advertise_endpoint,
        "source": config.source,
        "node_id": config.node_id,
        "tenant": config.tenant,
        "namespace": config.namespace,
        "cluster": {
            "id": config.cluster_id,
            "zone": config.cluster_zone,
            "peers": config.cluster_peers,
        },
        "paths": {
            "evidence": config.evidence_path,
            "status_report": config.status_report,
            "runtime_report": config.runtime_report,
            "middleware_report": config.middleware_report,
            "resource_report": config.resource_report,
        },
        "health": {
            "process_status": process_status,
            "restart_count": restart_count,
            "last_error": last_error,
        }
    });

    let body = serde_json::to_string_pretty(&payload)
        .map_err(|err| format!("serialize sidecar runtime payload failed: {err}"))?;
    fs::write(&config.registry_path, body).map_err(|err| {
        format!(
            "write sidecar runtime file {} failed: {err}",
            config.registry_path.display()
        )
    })
}

fn parse_u64_option(args: &[String], option: &str, default: u64) -> Result<u64, String> {
    match option_value(args, option) {
        Some(raw) => raw
            .parse::<u64>()
            .map_err(|err| format!("invalid value for {option}: {raw} ({err})")),
        None => Ok(default),
    }
}

fn parse_bool(raw: &str) -> Result<bool, String> {
    match raw.to_ascii_lowercase().as_str() {
        "true" | "1" | "yes" | "on" => Ok(true),
        "false" | "0" | "no" | "off" => Ok(false),
        _ => Err(format!("invalid bool value: {raw}")),
    }
}

fn parse_csv_list(raw: &str) -> Vec<String> {
    raw.split(',')
        .map(str::trim)
        .filter(|item| !item.is_empty())
        .map(ToString::to_string)
        .collect()
}

fn sidecar_mode_name(mode: &SidecarMode) -> &'static str {
    match mode {
        SidecarMode::Embedded => "embedded",
        SidecarMode::Standalone => "standalone",
    }
}

fn now_unix_nanos() -> u128 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_nanos())
        .unwrap_or(0)
}
