use anyhow::Result;
use serde::Deserialize;
use std::path::Path;

#[derive(Debug, Clone)]
pub struct RunnerSize {
    pub cpu: u32,
    pub memory_gb: u32,
    pub disk_gb: u32,
    pub time_budget_min: u32,
}

#[derive(Deserialize, Default)]
struct BuildSpec {
    cpu: Option<u32>,
    memory_gb: Option<u32>,
    disk_gb: Option<u32>,
    time_budget_min: Option<u32>,
    language: Option<String>,
}

impl RunnerSize {
    fn language_default(language: &str) -> Self {
        match language {
            "rust" => Self { cpu: 4, memory_gb: 8, disk_gb: 50, time_budget_min: 60 },
            "go" => Self { cpu: 2, memory_gb: 4, disk_gb: 30, time_budget_min: 30 },
            "java" | "kotlin" | "scala" => Self { cpu: 4, memory_gb: 8, disk_gb: 40, time_budget_min: 45 },
            _ => Self { cpu: 2, memory_gb: 4, disk_gb: 30, time_budget_min: 30 },
        }
    }

    pub fn from_p95(cpu_p95: f64, memory_mb_p95: f64) -> Self {
        let cpu = ((cpu_p95 * 1.2).ceil() as u32).max(2);
        let memory_gb = (((memory_mb_p95 * 1.2) / 1024.0).ceil() as u32).max(4);
        Self { cpu, memory_gb, disk_gb: 30, time_budget_min: 60 }
    }
}

fn parse_build_spec(context_path: &Path) -> Option<BuildSpec> {
    let spec_path = context_path.join(".build-spec.ncl");
    if !spec_path.exists() {
        return None;
    }
    // Extract JSON from nickel export — buildkit-launcher shells out to nickel
    let output = std::process::Command::new("nickel")
        .args(["export", "--format", "json"])
        .arg(&spec_path)
        .output()
        .ok()?;
    if !output.status.success() {
        return None;
    }
    serde_json::from_slice(&output.stdout).ok()
}

pub fn resolve(
    context_path: &Path,
    p95_cpu: Option<f64>,
    p95_mem_mb: Option<f64>,
    language_hint: Option<&str>,
) -> Result<RunnerSize> {
    let spec = parse_build_spec(context_path).unwrap_or_default();

    // Tier 1: explicit declaration in .build-spec.ncl
    if spec.cpu.is_some() || spec.memory_gb.is_some() {
        let base = language_hint
            .or(spec.language.as_deref())
            .map(RunnerSize::language_default)
            .unwrap_or_else(|| RunnerSize::language_default("default"));
        return Ok(RunnerSize {
            cpu: spec.cpu.unwrap_or(base.cpu),
            memory_gb: spec.memory_gb.unwrap_or(base.memory_gb),
            disk_gb: spec.disk_gb.unwrap_or(base.disk_gb),
            time_budget_min: spec.time_budget_min.unwrap_or(base.time_budget_min),
        });
    }

    // Tier 2: P95 historical × 1.2
    if let (Some(cpu_p95), Some(mem_p95)) = (p95_cpu, p95_mem_mb) {
        let mut size = RunnerSize::from_p95(cpu_p95, mem_p95);
        if let Some(budget) = spec.time_budget_min {
            size.time_budget_min = budget;
        }
        return Ok(size);
    }

    // Tier 3: language defaults
    let lang = language_hint
        .or(spec.language.as_deref())
        .unwrap_or("default");
    Ok(RunnerSize::language_default(lang))
}
