#![allow(dead_code)]

//! Shared helpers for the `entropyk-solver` Phase-0 Criterion benchmarks.
//!
//! Provides:
//! - A deterministic mock refrigeration cycle for micro-benchmarks (LU solve,
//!   residual/Jacobian assembly).
//! - Three reference cycles built directly from the public component/solver APIs
//!   so the end-to-end benchmarks exercise real CoolProp solves without paying
//!   the cost of spawning a CLI process.

use std::path::{Path, PathBuf};
use std::process::Command;
use std::sync::Arc;
use std::time::Duration;

use criterion::Criterion;
use entropyk_components::port::{Connected, FluidId, Port};
use entropyk_components::{
    Component, ComponentError, Condenser, ConnectedPort, Evaporator, IsenthalpicExpansionValve,
    IsentropicCompressor, JacobianBuilder, ResidualVector, StateSlice,
};
use entropyk_core::{Enthalpy, MassFlow, Pressure};
use entropyk_fluids::{CoolPropBackend, FluidBackend};
use entropyk_solver::system::{System, DEFAULT_MASS_FLOW_SEED_KG_S};
use entropyk_solver::{FallbackConfig, FallbackSolver, JacobianMatrix, NewtonConfig, Solver};

type CP = Port<Connected>;

// ── Mock components (copied from `refrigeration_cycle_integration.rs`) ───────

struct MockCompressor {
    port_suc: CP,
    port_disc: CP,
}

impl Component for MockCompressor {
    fn compute_residuals(
        &self,
        _s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        r[0] = self.port_disc.pressure().to_pascals()
            - (self.port_suc.pressure().to_pascals() + 1_000_000.0);
        r[1] = self.port_disc.enthalpy().to_joules_per_kg()
            - (self.port_suc.enthalpy().to_joules_per_kg() + 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,
}

impl Component for MockCondenser {
    fn compute_residuals(
        &self,
        _s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        r[0] = self.port_out.pressure().to_pascals() - self.port_in.pressure().to_pascals();
        r[1] = self.port_out.enthalpy().to_joules_per_kg()
            - (self.port_in.enthalpy().to_joules_per_kg() - 225_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,
}

impl Component for MockValve {
    fn compute_residuals(
        &self,
        _s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        r[0] = self.port_out.pressure().to_pascals()
            - (self.port_in.pressure().to_pascals() - 1_000_000.0);
        r[1] = self.port_out.enthalpy().to_joules_per_kg()
            - self.port_in.enthalpy().to_joules_per_kg();
        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,
}

impl Component for MockEvaporator {
    fn compute_residuals(
        &self,
        _s: &StateSlice,
        r: &mut ResidualVector,
    ) -> Result<(), ComponentError> {
        r[0] = self.port_out.pressure().to_pascals() - self.port_in.pressure().to_pascals();
        r[1] = self.port_out.enthalpy().to_joules_per_kg()
            - (self.port_in.enthalpy().to_joules_per_kg() + 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] {
        &[]
    }

    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 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
}

/// Builds the deterministic 4-component mock cycle used for Jacobian/assembly
/// micro-benchmarks. The system is analytically closed and converges in one
/// iteration from the exact initial state.
pub fn build_mock_system() -> (System, Vec<f64>) {
    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),
    });
    let cond = Box::new(MockCondenser {
        port_in: port(p_hp, 485_000.0),
        port_out: port(p_hp, 260_000.0),
    });
    let valv = Box::new(MockValve {
        port_in: port(p_hp, 260_000.0),
        port_out: port(p_lp, 260_000.0),
    });
    let evap = Box::new(MockEvaporator {
        port_in: port(p_lp, 260_000.0),
        port_out: 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.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.finalize().unwrap();

    let m = DEFAULT_MASS_FLOW_SEED_KG_S;
    let initial_state = vec![
        m, p_hp, 485_000.0, p_hp, 260_000.0, p_lp, 260_000.0, p_lp, 410_000.0,
    ];

    (system, initial_state)
}

/// Assembles a dense `JacobianMatrix` from the mock system at the given state.
pub fn assemble_jacobian(system: &System, state: &[f64]) -> JacobianMatrix {
    let mut builder = JacobianBuilder::new();
    system.assemble_jacobian(state, &mut builder).unwrap();

    let n = system.full_state_vector_len();
    let mut entries = Vec::new();
    for (r, c, v) in builder.entries() {
        entries.push((*r, *c, *v));
    }

    JacobianMatrix::from_builder(&entries, n, n)
}

// ── Reference-cycle construction (no CLI spawn) ──────────────────────────────

fn make_connected_port(fluid: &str, p_pa: f64, h_j_kg: f64) -> ConnectedPort {
    let a = Port::new(
        FluidId::new(fluid),
        Pressure::from_pascals(p_pa),
        Enthalpy::from_joules_per_kg(h_j_kg),
    );
    let b = Port::new(
        FluidId::new(fluid),
        Pressure::from_pascals(p_pa),
        Enthalpy::from_joules_per_kg(h_j_kg),
    );
    a.connect(b).unwrap().0
}

fn coolprop_backend() -> Arc<dyn FluidBackend> {
    Arc::new(CoolPropBackend::new())
}

/// Builds a real emergent-pressure R134a cycle. Three parameter sets give three
/// distinct reference cycles that all converge reliably without secondary-side
/// boundary components, keeping the benchmark focused on the Newton solver.
fn build_emergent_cycle(
    cond_sec_temp_k: f64,
    evap_sec_temp_k: f64,
    ua_cond: f64,
    ua_evap: f64,
) -> System {
    use entropyk_components::isentropic_compressor::VolumetricEfficiency;

    let backend = coolprop_backend();
    let fluid = "R134a";

    let comp = Box::new(
        IsentropicCompressor::new(0.70, 318.15, 278.15, 5.0)
            .with_refrigerant(fluid)
            .with_fluid_backend(backend.clone())
            .with_displacement(6.5e-5, 50.0, VolumetricEfficiency::Constant(0.92)),
    );

    let cond = Box::new(
        Condenser::new(ua_cond)
            .with_refrigerant(fluid)
            .with_fluid_backend(backend.clone())
            .with_secondary_stream(cond_sec_temp_k, 1500.0)
            .with_emergent_pressure(5.0),
    );

    let exv = Box::new(
        IsenthalpicExpansionValve::new(278.15)
            .with_refrigerant(fluid)
            .with_fluid_backend(backend.clone())
            .with_emergent_pressure(),
    );

    let evap = Box::new(
        Evaporator::new(ua_evap)
            .with_refrigerant(fluid)
            .with_fluid_backend(backend.clone())
            .with_secondary_stream(evap_sec_temp_k, 2000.0)
            .with_emergent_pressure(),
    );

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

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

    system.finalize().unwrap();
    system
}

pub fn build_reference_cycle_a() -> System {
    build_emergent_cycle(303.15, 285.15, 766.0, 1468.0)
}

pub fn build_reference_cycle_b() -> System {
    build_emergent_cycle(313.15, 283.15, 900.0, 1600.0)
}

pub fn build_reference_cycle_c() -> System {
    build_emergent_cycle(308.15, 291.15, 850.0, 1800.0)
}

/// Solves a reference cycle with the default fallback solver and a good initial seed.
pub fn solve_reference_system(system: &mut System) {
    let initial_state = vec![
        0.05,   // shared mass flow [kg/s]
        11.6e5, // comp->cond pressure [Pa]
        445e3,  // comp->cond enthalpy [J/kg]
        11.6e5, // cond->exv pressure [Pa]
        262e3,  // cond->exv enthalpy [J/kg]
        3.5e5,  // exv->evap pressure [Pa]
        262e3,  // exv->evap enthalpy [J/kg]
        3.5e5,  // evap->comp pressure [Pa]
        405e3,  // evap->comp enthalpy [J/kg]
    ];
    let newton = NewtonConfig {
        max_iterations: 200,
        tolerance: 1e-6,
        initial_state: Some(initial_state),
        ..NewtonConfig::default()
    };
    let mut solver = FallbackSolver::new(FallbackConfig::default()).with_newton_config(newton);
    solver.solve(system).expect("reference cycle must converge");
}

// ── Criterion configuration ─────────────────────────────────────────────────

/// Returns a Criterion configuration suitable for expensive end-to-end solves.
///
/// Uses a small sample count and a bounded measurement window so that the
/// reference-cycle benchmarks complete in a reasonable time while still
/// producing stable Phase-0 baseline numbers.
pub fn end_to_end_criterion() -> Criterion {
    Criterion::default()
        .sample_size(10)
        .measurement_time(Duration::from_secs(3))
        .warm_up_time(Duration::from_secs(1))
}

// ── CLI helpers (kept for optional manual verification) ──────────────────────

/// Returns the path to the release `entropyk-cli` binary relative to the
/// workspace root. Benchmarks are executed from `crates/solver`, so the
/// workspace root is two directories up.
pub fn cli_binary_path() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../..")
        .join("target/release/entropyk-cli.exe")
        .canonicalize()
        .unwrap_or_else(|_| {
            Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/release/entropyk-cli")
        })
}

/// Resolves a reference-cycle config path relative to the workspace root.
pub fn reference_config_path(name: &str) -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../..")
        .join("crates/cli/examples")
        .join(name)
}

/// Runs one reference cycle through the release CLI and asserts convergence.
pub fn run_cli_cycle(config_name: &str) {
    let binary = cli_binary_path();
    let config = reference_config_path(config_name);

    assert!(
        binary.exists(),
        "release CLI binary not found at {}. Build it with: cargo build --release --bin entropyk-cli",
        binary.display()
    );
    assert!(config.exists(), "config not found: {}", config.display());

    let output = Command::new(&binary)
        .arg("run")
        .arg("--config")
        .arg(&config)
        .output()
        .expect("failed to execute CLI binary");

    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    assert!(
        output.status.success(),
        "CLI solve failed for {}\nstdout:\n{}\nstderr:\n{}",
        config_name,
        stdout,
        stderr
    );
    assert!(
        stdout.contains("Status:     CONVERGED"),
        "CLI did not report CONVERGED for {}\nstdout:\n{}",
        config_name,
        stdout
    );
}
