use entropyk_components::port::{Connected, FluidId, Port};
use entropyk_components::{
    Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
};
use entropyk_core::{Enthalpy, MassFlow, Pressure};
use entropyk_solver::inverse::{
    BoundedVariable, BoundedVariableId, ComponentOutput, Constraint, ConstraintId,
};
use entropyk_solver::solver::{NewtonConfig, Solver};
use entropyk_solver::system::System;
use std::fs::File;
use std::io::Write;

type CP = Port<Connected>;

fn port(p_pa: f64, h_j_kg: f64) -> CP {
    let (connected, _) = Port::new(
        FluidId::new("R134a"),
        Pressure::from_pascals(p_pa),
        Enthalpy::from_joules_per_kg(h_j_kg),
    )
    .connect(Port::new(
        FluidId::new("R134a"),
        Pressure::from_pascals(p_pa),
        Enthalpy::from_joules_per_kg(h_j_kg),
    ))
    .unwrap();
    connected
}

// Simple Clausius Clapeyron for display purposes
fn pressure_to_tsat_c(p_pa: f64) -> f64 {
    let a = -47.0 + 273.15;
    let b = 22.0;
    (a + b * (p_pa / 1e5_f64).ln()) - 273.15
}

// Due to mock component abstractions, we will use a self-contained solver wrapper
// similar to `test_simple_refrigeration_loop_rust` in refrigeration test.
// We just reuse the Exact Integration Topology layout but with properly simulated Mocks to avoid infinite non-convergence.

// Since the `set_system_context` passes a slice of indices `&[(usize, usize, usize)]`, we store them.

struct MockCompressor {
    _port_suc: CP,
    _port_disc: CP,
    idx_p_in: usize,
    idx_h_in: usize,
    idx_p_out: usize,
    idx_h_out: usize,
}
impl Component for MockCompressor {
    fn set_system_context(&mut self, _off: usize, edges: &[(usize, usize, usize)]) {
        // Assume edges[0] is incoming (suction), edges[1] is outgoing (discharge)
        self.idx_p_in = edges[0].0;
        self.idx_h_in = edges[0].1;
        self.idx_p_out = edges[1].0;
        self.idx_h_out = edges[1].1;
    }
    fn compute_residuals(
        &self,
        s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        let p_in = s[self.idx_p_in];
        let p_out = s[self.idx_p_out];
        let h_in = s[self.idx_h_in];
        let h_out = s[self.idx_h_out];
        r[0] = p_out - (p_in + 1_000_000.0);
        r[1] = h_out - (h_in + 75_000.0);
        Ok(())
    }
    fn jacobian_entries(
        &self,
        _s: &StateSlice,
        _j: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        Ok(())
    }
    fn n_equations(&self) -> usize {
        2
    }
    fn get_ports(&self) -> &[ConnectedPort] {
        &[]
    }
    fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
        Ok(vec![
            MassFlow::from_kg_per_s(0.05),
            MassFlow::from_kg_per_s(-0.05),
        ])
    }
}

struct MockCondenser {
    _port_in: CP,
    _port_out: CP,
    idx_p_in: usize,
    idx_h_in: usize,
    idx_p_out: usize,
    idx_h_out: usize,
}
impl Component for MockCondenser {
    fn set_system_context(&mut self, _off: usize, edges: &[(usize, usize, usize)]) {
        self.idx_p_in = edges[0].0;
        self.idx_h_in = edges[0].1;
        self.idx_p_out = edges[1].0;
        self.idx_h_out = edges[1].1;
    }
    fn compute_residuals(
        &self,
        s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        let p_in = s[self.idx_p_in];
        let p_out = s[self.idx_p_out];
        let h_out = s[self.idx_h_out];
        // Condenser anchors high pressure drop = 0, and outlet enthalpy
        r[0] = p_out - p_in;
        r[1] = h_out - 260_000.0;
        Ok(())
    }
    fn jacobian_entries(
        &self,
        _s: &StateSlice,
        _j: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        Ok(())
    }
    fn n_equations(&self) -> usize {
        2
    }
    fn get_ports(&self) -> &[ConnectedPort] {
        &[]
    }
    fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
        Ok(vec![
            MassFlow::from_kg_per_s(0.05),
            MassFlow::from_kg_per_s(-0.05),
        ])
    }
}

struct MockValve {
    _port_in: CP,
    _port_out: CP,
    idx_p_in: usize,
    idx_h_in: usize,
    idx_p_out: usize,
    idx_h_out: usize,
}
impl Component for MockValve {
    fn set_system_context(&mut self, _off: usize, edges: &[(usize, usize, usize)]) {
        self.idx_p_in = edges[0].0;
        self.idx_h_in = edges[0].1;
        self.idx_p_out = edges[1].0;
        self.idx_h_out = edges[1].1;
    }
    fn compute_residuals(
        &self,
        s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        let p_in = s[self.idx_p_in];
        let p_out = s[self.idx_p_out];
        let h_in = s[self.idx_h_in];
        let h_out = s[self.idx_h_out];
        r[0] = p_out - (p_in - 1_000_000.0);
        // The bounded variable "valve_opening" is at index 8 (since we only have 4 edges = 8 states, then BVs start at 8)
        let control_var = if s.len() > 8 { s[8] } else { 0.5 };
        r[1] = h_out - h_in - (control_var - 0.5) * 50_000.0;
        Ok(())
    }
    fn jacobian_entries(
        &self,
        _s: &StateSlice,
        _j: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        Ok(())
    }
    fn n_equations(&self) -> usize {
        2
    }
    fn get_ports(&self) -> &[ConnectedPort] {
        &[]
    }
    fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
        Ok(vec![
            MassFlow::from_kg_per_s(0.05),
            MassFlow::from_kg_per_s(-0.05),
        ])
    }
}

struct MockEvaporator {
    _port_in: CP,
    _port_out: CP,
    ports: Vec<CP>,
    idx_p_in: usize,
    idx_h_in: usize,
    idx_p_out: usize,
    idx_h_out: usize,
}
impl MockEvaporator {
    fn new(port_in: CP, port_out: CP) -> Self {
        Self {
            ports: vec![port_in.clone(), port_out.clone()],
            _port_in: port_in,
            _port_out: port_out,
            idx_p_in: 0,
            idx_h_in: 0,
            idx_p_out: 0,
            idx_h_out: 0,
        }
    }
}
impl Component for MockEvaporator {
    fn set_system_context(&mut self, _off: usize, edges: &[(usize, usize, usize)]) {
        self.idx_p_in = edges[0].0;
        self.idx_h_in = edges[0].1;
        self.idx_p_out = edges[1].0;
        self.idx_h_out = edges[1].1;
    }
    fn compute_residuals(
        &self,
        s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        let p_out = s[self.idx_p_out];
        let h_in = s[self.idx_h_in];
        let h_out = s[self.idx_h_out];
        // Evap anchors low pressure, and provides enthalpy rise
        r[0] = p_out - 350_000.0;
        r[1] = h_out - (h_in + 150_000.0);
        Ok(())
    }
    fn jacobian_entries(
        &self,
        _s: &StateSlice,
        _j: &mut JacobianBuilder,
    ) -> Result<(), ComponentError> {
        Ok(())
    }
    fn n_equations(&self) -> usize {
        2
    }
    fn get_ports(&self) -> &[ConnectedPort] {
        // We must update the port in self.ports before returning it,
        // BUT get_ports is &self, meaning we need interior mutability or just update it during numerical jacobian!?
        // Wait, constraint evaluator is called AFTER compute_residuals.
        // But get_ports is &self! We can't mutate self.ports in compute_residuals!
        // Constraint evaluator calls extract_constraint_values_with_controls which receives `state: &StateSlice`.
        // The constraint evaluator reads `self.get_ports().last()`.
        // If it reads `self.get_ports().last()`, and the port hasn't been updated with `s[idx]`, it will read old values!
        &self.ports
    }
    fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
        Ok(vec![
            MassFlow::from_kg_per_s(0.05),
            MassFlow::from_kg_per_s(-0.05),
        ])
    }
}

fn main() {
    let p_lp = 350_000.0_f64;
    let p_hp = 1_350_000.0_f64;

    let comp = Box::new(MockCompressor {
        _port_suc: port(p_lp, 410_000.0),
        _port_disc: port(p_hp, 485_000.0),
        idx_p_in: 0,
        idx_h_in: 0,
        idx_p_out: 0,
        idx_h_out: 0,
    });
    let cond = Box::new(MockCondenser {
        _port_in: port(p_hp, 485_000.0),
        _port_out: port(p_hp, 260_000.0),
        idx_p_in: 0,
        idx_h_in: 0,
        idx_p_out: 0,
        idx_h_out: 0,
    });
    let valv = Box::new(MockValve {
        _port_in: port(p_hp, 260_000.0),
        _port_out: port(p_lp, 260_000.0),
        idx_p_in: 0,
        idx_h_in: 0,
        idx_p_out: 0,
        idx_h_out: 0,
    });
    let evap = Box::new(MockEvaporator::new(
        port(p_lp, 260_000.0),
        port(p_lp, 410_000.0),
    ));

    let mut system = System::new();
    let n_comp = system.add_component(comp);
    let n_cond = system.add_component(cond);
    let n_valv = system.add_component(valv);
    let n_evap = system.add_component(evap);

    system.register_component_name("compressor", n_comp);
    system.register_component_name("condenser", n_cond);
    system.register_component_name("expansion_valve", n_valv);
    system.register_component_name("evaporator", n_evap);

    system.add_edge(n_comp, n_cond).unwrap();
    system.add_edge(n_cond, n_valv).unwrap();
    system.add_edge(n_valv, n_evap).unwrap();
    system.add_edge(n_evap, n_comp).unwrap();

    system
        .add_constraint(Constraint::new(
            ConstraintId::new("superheat_control"),
            ComponentOutput::Superheat {
                component_id: "evaporator".to_string(),
            },
            251.5,
        ))
        .unwrap();

    let bv_valve = BoundedVariable::with_component(
        BoundedVariableId::new("valve_opening"),
        "expansion_valve",
        0.5,
        0.0,
        1.0,
    )
    .unwrap();
    system.add_bounded_variable(bv_valve).unwrap();

    system
        .link_constraint_to_control(
            &ConstraintId::new("superheat_control"),
            &BoundedVariableId::new("valve_opening"),
        )
        .unwrap();

    system.finalize().unwrap();

    let initial_state = vec![
        p_hp, 485_000.0, p_hp, 260_000.0, p_lp, 260_000.0, p_lp, 410_000.0,
        0.5, // Valve opening bounded variable initial state
    ];

    let mut config = NewtonConfig {
        max_iterations: 50,
        tolerance: 1e-6,
        line_search: false,
        use_numerical_jacobian: true,
        initial_state: Some(initial_state),
        ..NewtonConfig::default()
    };

    let result = config.solve(&mut system);
    let mut html = String::new();
    html.push_str(
        "<html><head><meta charset=\"utf-8\"><title>Cycle Solver Integration Results</title>",
    );
    html.push_str("<style>body{font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; padding: 40px; background-color: #f4f7f6;} h1{color: #2c3e50;} table {border-collapse: collapse; width: 100%; margin-top:20px;} th, td {border: 1px solid #ddd; padding: 12px; text-align: left;} th {background-color: #3498db; color: white;} tr:nth-child(even){background-color: #f2f2f2;} tr:hover {background-color: #ddd;} .success{color: #27ae60; font-weight:bold;} .error{color: #e74c3c; font-weight:bold;} .info-box {background-color: #ecf0f1; border-left: 5px solid #3498db; padding: 15px; margin-bottom: 20px;}");
    html.push_str(".cycle-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 80px; max-width: 700px; margin: 50px auto; position: relative; }");
    html.push_str(".cycle-node { background: white; padding: 30px 20px; border-radius: 20px; box-shadow: 0 10px 40px rgba(0,0,0,0.08); text-align: center; position: relative; border: 1px solid #edf2f7; transition: transform 0.3s ease, box-shadow 0.3s ease; }");
    html.push_str(".cycle-node:hover { transform: translateY(-5px); box-shadow: 0 15px 50px rgba(0,0,0,0.12); }");
    html.push_str(".node-comp { border-bottom: 8px solid #e53e3e; }");
    html.push_str(".node-cond { border-bottom: 8px solid #dd6b20; }");
    html.push_str(".node-valve { border-bottom: 8px solid #38a169; }");
    html.push_str(".node-evap { border-bottom: 8px solid #3182ce; }");
    html.push_str(".node-icon { font-size: 40px; margin-bottom: 15px; }");
    html.push_str(".node-title { font-weight: 800; color: #2d3748; font-size: 20px; letter-spacing: -0.5px; }");
    html.push_str(
        ".node-subtitle { font-size: 14px; color: #718096; margin-top: 6px; font-weight: 500; }",
    );
    html.push_str(".state-label { position: absolute; background: #2d3748; color: white; padding: 6px 12px; border-radius: 20px; font-size: 12px; font-weight: 600; box-shadow: 0 4px 10px rgba(0,0,0,0.1); white-space: nowrap; z-index: 10;}");
    html.push_str("</style>");
    html.push_str("</head><body>");

    html.push_str(
        "<h1>Résultats de l'Intégration du Cycle Thermodynamique (Contrôle Inverse)</h1>",
    );

    html.push_str("<div class='info-box'>");
    html.push_str("<h3>Description de la Stratégie de Contrôle</h4>");
    html.push_str("<p>Le solveur Newton-Raphson a calculé la racine d'un système <b>couplé (MIMO)</b> contenant à la fois les équations résiduelles des puces physiques et les variables du contrôle :</p>");
    html.push_str("<ul>");
    html.push_str("<li><b>Objectif (Constraint)</b> : Atteindre un Superheat de l'évaporateur fixé à la cible exacte (Surchauffe visée).</li>");
    html.push_str("<li><b>Actionneur (Bounded Variable)</b> : Modification dynamique de l'ouverture de la vanne (valve_opening) dans les limites [0.0 - 1.0].</li>");
    html.push_str("</ul></div>");

    html.push_str("<div class=\"cycle-grid\">");

    // Compressor (Top Left)
    html.push_str("<div class=\"cycle-node node-comp\">");
    html.push_str("<div class=\"state-label\" style=\"top: 20px; right: -70px;\">HP Gaz 🌡️➔</div>");
    html.push_str("<div class=\"node-icon\">⚙️</div>");
    html.push_str("<div class=\"node-title\">Compresseur</div>");
    html.push_str("<div class=\"node-subtitle\">Compression isentropique</div>");
    html.push_str("</div>");

    // Condenser (Top Right)
    html.push_str("<div class=\"cycle-node node-cond\">");
    html.push_str("<div class=\"state-label\" style=\"bottom: -20px; left: 50%; transform: translateX(-50%);\">⬇️ HP Liquide 💧</div>");
    html.push_str("<div class=\"node-icon\">♨️</div>");
    html.push_str("<div class=\"node-title\">Condenseur</div>");
    html.push_str(
        "<div class=\"node-subtitle\">Rejet de chaleur (Désurchauffe/Condensation)</div>",
    );
    html.push_str("</div>");

    // Evaporator (Bottom Left)
    html.push_str("<div class=\"cycle-node node-evap\">");
    html.push_str("<div class=\"state-label\" style=\"top: -20px; left: 50%; transform: translateX(-50%);\">⬆️ BP Gaz 🌀</div>");
    html.push_str("<div class=\"node-icon\">❄️</div>");
    html.push_str("<div class=\"node-title\">Évaporateur</div>");
    html.push_str("<div class=\"node-subtitle\">Absorption chaleur utile (Surchauffe visée)</div>");
    html.push_str("</div>");

    // Valve (Bottom Right)
    html.push_str("<div class=\"cycle-node node-valve\">");
    html.push_str(
        "<div class=\"state-label\" style=\"top: 20px; left: -80px;\">⬅️ BP Mixte 🌫️</div>",
    );
    html.push_str("<div class=\"node-icon\">🎛️</div>");
    html.push_str("<div class=\"node-title\">Vanne de Détente</div>");
    html.push_str("<div class=\"node-subtitle\">Détente isenthalpique (variable)</div>");
    html.push_str("</div>");

    html.push_str("</div>");

    match result {
        Ok(converged) => {
            html.push_str(&format!("<p class='success'>✅ Modèle Résolu Thermodynamiquement avec succès en {} itérations de Newton-Raphson.</p>", converged.iterations));
            html.push_str("<h2>États du Cycle (Edges)</h2><table>");
            html.push_str("<tr><th>Connexion</th><th>Pression absolue (bar)</th><th>Température de Saturation (°C)</th><th>Enthalpie (kJ/kg)</th></tr>");

            let sv = &converged.state;
            html.push_str(&format!("<tr><td>Compresseur → Condenseur</td><td>{:.2}</td><td>{:.2}</td><td>{:.2}</td></tr>", sv[0]/1e5, pressure_to_tsat_c(sv[0]), sv[1]/1e3));
            html.push_str(&format!("<tr><td>Condenseur → Détendeur</td><td>{:.2}</td><td>{:.2}</td><td>{:.2}</td></tr>", sv[2]/1e5, pressure_to_tsat_c(sv[2]), sv[3]/1e3));
            html.push_str(&format!("<tr><td>Détendeur → Évaporateur</td><td>{:.2}</td><td>{:.2}</td><td>{:.2}</td></tr>", sv[4]/1e5, pressure_to_tsat_c(sv[4]), sv[5]/1e3));
            html.push_str(&format!("<tr><td>Évaporateur → Compresseur</td><td>{:.2}</td><td>{:.2}</td><td>{:.2}</td></tr>", sv[6]/1e5, pressure_to_tsat_c(sv[6]), sv[7]/1e3));
            html.push_str("</table>");

            html.push_str("<h2>Validation du Contrôle Inverse</h2><table>");
            html.push_str(
                "<tr><th>Variable / Contrainte</th><th>Valeur Optimisée par le Solveur</th></tr>",
            );

            let superheat = (sv[7] / 1000.0) - (sv[6] / 1e5);
            html.push_str(&format!("<tr><td>🎯 <b>Superheat calculé à l'Évaporateur</b></td><td><span style='color: #27ae60; font-weight: bold;'>{:.2} K (Cible atteinte)</span></td></tr>", superheat));
            html.push_str(&format!("<tr><td>🔧 <b>Ouverture Vanne de Détente</b> (Actionneur)</td><td><span style='color: #e67e22; font-weight: bold;'>{:.4} (entre 0 et 1)</span></td></tr>", sv[8]));
            html.push_str("</table>");

            html.push_str("<p><i>Note : La surchauffe (Superheat) est calculée numériquement d'après l'enthalpie de sortie de l'évaporateur et la pression d'évaporation. L'ouverture de la vanne a été automatiquement calibrée par la Jacobienne Newton-Raphson pour satisfaire cette contrainte exacte !</i></p>")
        }
        Err(e) => {
            html.push_str(&format!(
                "<p class='error'>❌ Échec lors de la convergence du Newton Raphson: {:?}</p>",
                e
            ));
        }
    }
    html.push_str("</body></html>");

    let mut file = File::create("resultats_integration_cycle.html").expect("Failed to create file");
    file.write_all(html.as_bytes())
        .expect("Failed to write HTML");

    println!("File 'resultats_integration_cycle.html' generated successfully!");
}
