//! Serveur UI Entropyk - Utilise les composants Rust réels pour les calculs.
//!
//! Lance l'UI et une API qui exécute les calculs avec les vrais composants.
//!
//! cargo run -p entropyk-demo --bin ui-server

use axum::{
    extract::Json,
    routing::post,
    Router,
};
use entropyk_components::compressor::SstSdtCoefficients;
use entropyk_components::pipe::{friction_factor, Pipe, PipeGeometry};
use entropyk_components::pump::{Pump, PumpCurves};
use entropyk_components::port::{FluidId, Port};
use entropyk_core::{Enthalpy, Pressure};
use serde::{Deserialize, Serialize};
use std::path::Path;
use tower_http::services::ServeDir;

#[derive(Debug, Deserialize)]
struct ComponentConfig {
    id: String,
    #[serde(rename = "type")]
    comp_type: String,
    label: String,
    config: serde_json::Value,
}

#[derive(Debug, Deserialize)]
struct CalculateRequest {
    components: Vec<ComponentConfig>,
}

#[derive(Debug, Serialize)]
struct ComponentResult {
    id: String,
    label: String,
    #[serde(rename = "type")]
    comp_type: String,
    results: serde_json::Value,
    error: Option<String>,
}

#[derive(Debug, Serialize)]
struct CalculateResponse {
    results: Vec<ComponentResult>,
}

fn parse_coeffs(s: &str) -> Vec<f64> {
    s.split(',')
        .filter_map(|x| x.trim().parse::<f64>().ok())
        .collect()
}

async fn calculate(
    axum::extract::Json(req): axum::extract::Json<CalculateRequest>,
) -> axum::Json<CalculateResponse> {
    let mut results = Vec::new();

    for comp in req.components {
        let result = match comp.comp_type.as_str() {
            "pump" => calc_pump(&comp),
            "compressor" => calc_compressor(&comp),
            "pipe" => calc_pipe(&comp),
            "valve" => calc_valve(&comp),
            _ => ComponentResult {
                id: comp.id.clone(),
                label: comp.label.clone(),
                comp_type: comp.comp_type.clone(),
                results: serde_json::json!({}),
                error: Some("Type inconnu".to_string()),
            },
        };
        results.push(result);
    }

    Json(CalculateResponse { results })
}

fn calc_pump(comp: &ComponentConfig) -> ComponentResult {
    let config = &comp.config;
    let head_coeffs = config
        .get("head_coeffs")
        .and_then(|v| v.as_str())
        .unwrap_or("30,-10,-50");
    let eff_coeffs = config
        .get("eff_coeffs")
        .and_then(|v| v.as_str())
        .unwrap_or("0.5,0.3,-0.5");
    let density = config
        .get("density")
        .and_then(|v| v.as_f64())
        .unwrap_or(1000.0);

    let h = parse_coeffs(head_coeffs);
    let e = parse_coeffs(eff_coeffs);

    if h.len() < 3 || e.len() < 3 {
        return ComponentResult {
            id: comp.id.clone(),
            label: comp.label.clone(),
            comp_type: "pump".to_string(),
            results: serde_json::json!({}),
            error: Some("Coefficients insuffisants (min 3 pour H et η)".to_string()),
        };
    }

    match PumpCurves::quadratic(h[0], h[1], h[2], e[0], e[1], e[2]) {
        Ok(curves) => {
            let inlet = Port::new(
                FluidId::new("Water"),
                Pressure::from_bar(1.0),
                Enthalpy::from_joules_per_kg(100_000.0),
            );
            let outlet = Port::new(
                FluidId::new("Water"),
                Pressure::from_bar(1.0),
                Enthalpy::from_joules_per_kg(100_000.0),
            );

            match Pump::new(curves, inlet, outlet, density) {
                Ok(pump) => {
                    let points: Vec<_> = [0.0, 0.05, 0.1, 0.15, 0.2]
                        .iter()
                        .map(|&q| {
                            serde_json::json!({
                                "Q_m3_s": q,
                                "H_m": pump.curves().head_at_flow(q),
                                "efficiency": pump.curves().efficiency_at_flow(q)
                            })
                        })
                        .collect();

                    ComponentResult {
                        id: comp.id.clone(),
                        label: comp.label.clone(),
                        comp_type: "pump".to_string(),
                        results: serde_json::json!({
                            "curve_points": points,
                            "density_kg_m3": density
                        }),
                        error: None,
                    }
                }
                Err(e) => ComponentResult {
                    id: comp.id.clone(),
                    label: comp.label.clone(),
                    comp_type: "pump".to_string(),
                    results: serde_json::json!({}),
                    error: Some(e.to_string()),
                },
            }
        }
        Err(e) => ComponentResult {
            id: comp.id.clone(),
            label: comp.label.clone(),
            comp_type: "pump".to_string(),
            results: serde_json::json!({}),
            error: Some(e.to_string()),
        },
    }
}

fn calc_compressor(comp: &ComponentConfig) -> ComponentResult {
    let config = &comp.config;
    let mass_s = config
        .get("mass_coeffs")
        .and_then(|v| v.as_str())
        .unwrap_or("0.05,0.001,0.0005,0.00001");
    let power_s = config
        .get("power_coeffs")
        .and_then(|v| v.as_str())
        .unwrap_or("1000,50,30,0.5");

    let m = parse_coeffs(mass_s);
    let p = parse_coeffs(power_s);

    if m.len() < 4 || p.len() < 4 {
        return ComponentResult {
            id: comp.id.clone(),
            label: comp.label.clone(),
            comp_type: "compressor".to_string(),
            results: serde_json::json!({}),
            error: Some("Coefficients SST/SDT: 4 valeurs (a00,a10,a01,a11)".to_string()),
        };
    }

    let sst_sdt = SstSdtCoefficients::bilinear(
        m[0], m[1], m[2], m[3],
        p[0], p[1], p[2], p[3],
    );

    let sst = 268.15;
    let sdt = 313.15;
    let mass_flow = sst_sdt.mass_flow_at(sst, sdt);
    let power = sst_sdt.power_at(sst, sdt);

    ComponentResult {
        id: comp.id.clone(),
        label: comp.label.clone(),
        comp_type: "compressor".to_string(),
        results: serde_json::json!({
            "SST_K": sst,
            "SDT_K": sdt,
            "mass_flow_kg_s": mass_flow,
            "power_W": power
        }),
        error: None,
    }
}

fn calc_pipe(comp: &ComponentConfig) -> ComponentResult {
    let config = &comp.config;
    let length = config.get("length").and_then(|v| v.as_f64()).unwrap_or(10.0);
    let diameter = config.get("diameter").and_then(|v| v.as_f64()).unwrap_or(0.022);
    let rough = config.get("roughness").and_then(|v| v.as_f64()).unwrap_or(1.5e-6);

    match PipeGeometry::new(length, diameter, rough) {
        Ok(geometry) => {
            let inlet = Port::new(
                FluidId::new("Water"),
                Pressure::from_bar(2.0),
                Enthalpy::from_joules_per_kg(84_000.0),
            );
            let outlet = Port::new(
                FluidId::new("Water"),
                Pressure::from_bar(2.0),
                Enthalpy::from_joules_per_kg(84_000.0),
            );

            match Pipe::new(geometry, inlet, outlet, 998.0, 0.001) {
                Ok(_pipe) => {
                    let flow = 0.01;
                    let area = geometry.area();
                    let velocity = flow / area;
                    let re = velocity * diameter * 998.0 / 0.001;
                    let rel_rough = rough / diameter;
                    let f = friction_factor::haaland(rel_rough, re);
                    let dp = f * (length / diameter) * (998.0 * velocity * velocity / 2.0);

                    ComponentResult {
                        id: comp.id.clone(),
                        label: comp.label.clone(),
                        comp_type: "pipe".to_string(),
                        results: serde_json::json!({
                            "length_m": length,
                            "diameter_m": diameter,
                            "pressure_drop_Pa_at_0.01_m3_s": dp,
                            "reynolds_at_0.01_m3_s": re
                        }),
                        error: None,
                    }
                }
                Err(e) => ComponentResult {
                    id: comp.id.clone(),
                    label: comp.label.clone(),
                    comp_type: "pipe".to_string(),
                    results: serde_json::json!({}),
                    error: Some(e.to_string()),
                },
            }
        }
        Err(e) => ComponentResult {
            id: comp.id.clone(),
            label: comp.label.clone(),
            comp_type: "pipe".to_string(),
            results: serde_json::json!({}),
            error: Some(e.to_string()),
        },
    }
}

fn calc_valve(comp: &ComponentConfig) -> ComponentResult {
    let config = &comp.config;
    let opening = config.get("opening").and_then(|v| v.as_f64()).unwrap_or(1.0);

    ComponentResult {
        id: comp.id.clone(),
        label: comp.label.clone(),
        comp_type: "valve".to_string(),
        results: serde_json::json!({
            "opening": opening,
            "note": "Détendeur isenthalpique - calcul complet avec solveur"
        }),
        error: None,
    }
}

#[tokio::main]
async fn main() {
    let port = std::env::var("PORT").unwrap_or_else(|_| "3030".to_string());
    let addr = format!("0.0.0.0:{}", port);

    let ui_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../ui");
    println!("Entropyk UI - http://localhost:{}", port);
    println!("Dossier UI: {}", ui_path.display());

    let app = Router::new()
        .route("/api/calculate", post(calculate))
        .nest_service("/", ServeDir::new(ui_path));

    let listener = match tokio::net::TcpListener::bind(&addr).await {
        Ok(l) => l,
        Err(e) => {
            eprintln!("Erreur: impossible de lier le port {} ({})", port, e);
            eprintln!("  → Port déjà utilisé? Essayez: PORT=3031 cargo run -p entropyk-demo --bin ui-server");
            eprintln!("  → Ou tuez le processus: lsof -ti:{} | xargs kill", port);
            std::process::exit(1);
        }
    };
    axum::serve(listener, app).await.unwrap();
}
