//! Solver FFI functions and result types.

use std::os::raw::{c_double, c_uint};
use std::panic;
use std::time::Duration;

use crate::error::EntropykErrorCode;
use crate::system::EntropykSystem;

/// Convergence status after solving.
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntropykConvergenceStatus {
    /// Solver converged successfully.
    Converged = 0,
    /// Solver timed out, returning best state found.
    ConvergedTimedOut = 1,
    /// Control variable reached saturation limit.
    ConvergedControlSaturation = 2,
}

impl From<entropyk_solver::ConvergenceStatus> for EntropykConvergenceStatus {
    fn from(status: entropyk_solver::ConvergenceStatus) -> Self {
        match status {
            entropyk_solver::ConvergenceStatus::Converged => EntropykConvergenceStatus::Converged,
            entropyk_solver::ConvergenceStatus::TimedOutWithBestState => {
                EntropykConvergenceStatus::ConvergedTimedOut
            }
            entropyk_solver::ConvergenceStatus::ControlSaturation => {
                EntropykConvergenceStatus::ConvergedControlSaturation
            }
        }
    }
}

/// Configuration for the Newton-Raphson solver.
#[repr(C)]
pub struct EntropykNewtonConfig {
    /// Maximum number of iterations.
    pub max_iterations: c_uint,
    /// Convergence tolerance.
    pub tolerance: c_double,
    /// Enable line search.
    pub line_search: bool,
    /// Timeout in milliseconds (0 = no timeout).
    pub timeout_ms: c_uint,
}

impl Default for EntropykNewtonConfig {
    fn default() -> Self {
        Self {
            max_iterations: 100,
            tolerance: 1e-6,
            line_search: false,
            timeout_ms: 0,
        }
    }
}

impl From<&EntropykNewtonConfig> for entropyk_solver::NewtonConfig {
    fn from(cfg: &EntropykNewtonConfig) -> Self {
        Self {
            max_iterations: cfg.max_iterations as usize,
            tolerance: cfg.tolerance,
            line_search: cfg.line_search,
            timeout: if cfg.timeout_ms > 0 {
                Some(Duration::from_millis(cfg.timeout_ms as u64))
            } else {
                None
            },
            ..Default::default()
        }
    }
}

/// Configuration for the Picard (sequential substitution) solver.
#[repr(C)]
pub struct EntropykPicardConfig {
    /// Maximum number of iterations.
    pub max_iterations: c_uint,
    /// Convergence tolerance.
    pub tolerance: c_double,
    /// Relaxation factor (0.0 to 1.0).
    pub relaxation: c_double,
}

impl Default for EntropykPicardConfig {
    fn default() -> Self {
        Self {
            max_iterations: 500,
            tolerance: 1e-4,
            relaxation: 0.5,
        }
    }
}

impl From<&EntropykPicardConfig> for entropyk_solver::PicardConfig {
    fn from(cfg: &EntropykPicardConfig) -> Self {
        Self {
            max_iterations: cfg.max_iterations as usize,
            tolerance: cfg.tolerance,
            relaxation_factor: cfg.relaxation,
            ..Default::default()
        }
    }
}

/// Configuration for the fallback solver (Newton → Picard).
#[repr(C)]
#[derive(Default)]
pub struct EntropykFallbackConfig {
    /// Newton solver configuration.
    pub newton: EntropykNewtonConfig,
    /// Picard solver configuration.
    pub picard: EntropykPicardConfig,
}

/// Opaque handle to a solver result.
///
/// Create via `entropyk_solve_*()` functions.
/// MUST call `entropyk_result_free()` when done.
#[repr(C)]
pub struct EntropykSolverResult {
    _private: [u8; 0],
}

struct SolverResultInner {
    state: Vec<f64>,
    iterations: usize,
    final_residual: f64,
    status: EntropykConvergenceStatus,
}

impl SolverResultInner {
    fn from_converged(cs: entropyk_solver::ConvergedState) -> Self {
        Self {
            state: cs.state,
            iterations: cs.iterations,
            final_residual: cs.final_residual,
            status: cs.status.into(),
        }
    }
}

/// Solve the system using Newton-Raphson method.
///
/// # Arguments
///
/// - `system`: The finalized system (must not be null)
/// - `config`: Solver configuration (must not be null)
/// - `result`: Output parameter for the result pointer (must not be null)
///
/// # Returns
///
/// - `ENTROPYK_OK` on success (result contains the solution)
/// - Error code on failure
///
/// # Safety
///
/// - `system` must be a valid pointer to a finalized system
/// - `config` must be a valid pointer
/// - `result` must be a valid pointer to a location where the result pointer will be stored
///
/// # Ownership
///
/// On success, `*result` contains a pointer that the caller owns.
/// MUST call `entropyk_result_free()` when done.
#[no_mangle]
pub unsafe extern "C" fn entropyk_solve_newton(
    system: *mut EntropykSystem,
    config: *const EntropykNewtonConfig,
    result: *mut *mut EntropykSolverResult,
) -> EntropykErrorCode {
    if system.is_null() || config.is_null() || result.is_null() {
        return EntropykErrorCode::EntropykNullPointer;
    }

    let sys = &mut *(system as *mut entropyk_solver::System);
    let cfg = &*config;
    let mut newton_config: entropyk_solver::NewtonConfig = cfg.into();

    let solve_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
        entropyk_solver::Solver::solve(&mut newton_config, sys)
    }));

    match solve_result {
        Ok(Ok(converged)) => {
            let inner = SolverResultInner::from_converged(converged);
            *result = Box::into_raw(Box::new(inner)) as *mut EntropykSolverResult;
            EntropykErrorCode::EntropykOk
        }
        Ok(Err(e)) => EntropykErrorCode::from(e),
        Err(_) => EntropykErrorCode::EntropykUnknown,
    }
}

/// Solve the system using Picard (sequential substitution) method.
///
/// # Arguments
///
/// - `system`: The finalized system (must not be null)
/// - `config`: Solver configuration (must not be null)
/// - `result`: Output parameter for the result pointer (must not be null)
///
/// # Returns
///
/// - `ENTROPYK_OK` on success (result contains the solution)
/// - Error code on failure
///
/// # Safety
///
/// - `system` must be a valid pointer to a finalized system
/// - `config` must be a valid pointer
/// - `result` must be a valid pointer
///
/// # Ownership
///
/// On success, caller owns `*result`. MUST call `entropyk_result_free()` when done.
#[no_mangle]
pub unsafe extern "C" fn entropyk_solve_picard(
    system: *mut EntropykSystem,
    config: *const EntropykPicardConfig,
    result: *mut *mut EntropykSolverResult,
) -> EntropykErrorCode {
    if system.is_null() || config.is_null() || result.is_null() {
        return EntropykErrorCode::EntropykNullPointer;
    }

    let sys = &mut *(system as *mut entropyk_solver::System);
    let cfg = &*config;
    let mut picard_config: entropyk_solver::PicardConfig = cfg.into();

    let solve_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
        entropyk_solver::Solver::solve(&mut picard_config, sys)
    }));

    match solve_result {
        Ok(Ok(converged)) => {
            let inner = SolverResultInner::from_converged(converged);
            *result = Box::into_raw(Box::new(inner)) as *mut EntropykSolverResult;
            EntropykErrorCode::EntropykOk
        }
        Ok(Err(e)) => EntropykErrorCode::from(e),
        Err(_) => EntropykErrorCode::EntropykUnknown,
    }
}

/// Solve the system using fallback strategy (Newton → Picard).
///
/// Starts with Newton-Raphson and falls back to Picard on divergence.
///
/// # Arguments
///
/// - `system`: The finalized system (must not be null)
/// - `config`: Solver configuration (must not be null, can use default)
/// - `result`: Output parameter for the result pointer (must not be null)
///
/// # Returns
///
/// - `ENTROPYK_OK` on success (result contains the solution)
/// - Error code on failure
///
/// # Safety
///
/// - `system` must be a valid pointer to a finalized system
/// - `config` must be a valid pointer
/// - `result` must be a valid pointer
///
/// # Ownership
///
/// On success, caller owns `*result`. MUST call `entropyk_result_free()` when done.
#[no_mangle]
pub unsafe extern "C" fn entropyk_solve_fallback(
    system: *mut EntropykSystem,
    config: *const EntropykFallbackConfig,
    result: *mut *mut EntropykSolverResult,
) -> EntropykErrorCode {
    if system.is_null() || config.is_null() || result.is_null() {
        return EntropykErrorCode::EntropykNullPointer;
    }

    let sys = &mut *(system as *mut entropyk_solver::System);
    let cfg = &*config;

    let newton_config: entropyk_solver::NewtonConfig = (&cfg.newton).into();
    let picard_config: entropyk_solver::PicardConfig = (&cfg.picard).into();

    let mut fallback =
        entropyk_solver::FallbackSolver::new(entropyk_solver::FallbackConfig::default())
            .with_newton_config(newton_config)
            .with_picard_config(picard_config);

    let solve_result = panic::catch_unwind(panic::AssertUnwindSafe(|| {
        entropyk_solver::Solver::solve(&mut fallback, sys)
    }));

    match solve_result {
        Ok(Ok(converged)) => {
            let inner = SolverResultInner::from_converged(converged);
            *result = Box::into_raw(Box::new(inner)) as *mut EntropykSolverResult;
            EntropykErrorCode::EntropykOk
        }
        Ok(Err(e)) => EntropykErrorCode::from(e),
        Err(_) => EntropykErrorCode::EntropykUnknown,
    }
}

/// Get the convergence status from a solver result.
///
/// # Safety
///
/// `result` must be a valid pointer from a solve function.
#[no_mangle]
pub unsafe extern "C" fn entropyk_result_get_status(
    result: *const EntropykSolverResult,
) -> EntropykConvergenceStatus {
    if result.is_null() {
        return EntropykConvergenceStatus::ConvergedTimedOut;
    }
    let inner = &*(result as *const SolverResultInner);
    inner.status
}

/// Get the number of iterations from a solver result.
///
/// # Safety
///
/// `result` must be a valid pointer from a solve function.
#[no_mangle]
pub unsafe extern "C" fn entropyk_result_get_iterations(
    result: *const EntropykSolverResult,
) -> c_uint {
    if result.is_null() {
        return 0;
    }
    let inner = &*(result as *const SolverResultInner);
    inner.iterations as c_uint
}

/// Get the final residual norm from a solver result.
///
/// # Safety
///
/// `result` must be a valid pointer from a solve function.
#[no_mangle]
pub unsafe extern "C" fn entropyk_result_get_residual(
    result: *const EntropykSolverResult,
) -> c_double {
    if result.is_null() {
        return f64::NAN;
    }
    let inner = &*(result as *const SolverResultInner);
    inner.final_residual
}

/// Get the state vector from a solver result.
///
/// # Arguments
///
/// - `result`: The solver result (must not be null)
/// - `out`: Output buffer for the state vector (can be null to query length)
/// - `len`: On input: capacity of `out` buffer. On output: actual length.
///
/// # Returns
///
/// - `ENTROPYK_OK` on success
/// - `ENTROPYK_NULL_POINTER` if result or len is null
/// - `ENTROPYK_INVALID_ARGUMENT` if buffer is too small
///
/// # Safety
///
/// - `result` must be a valid pointer from a solve function
/// - `out` must be a valid pointer to at least `*len` doubles, or null
/// - `len` must be a valid pointer
#[no_mangle]
pub unsafe extern "C" fn entropyk_result_get_state_vector(
    result: *const EntropykSolverResult,
    out: *mut c_double,
    len: *mut c_uint,
) -> EntropykErrorCode {
    if result.is_null() || len.is_null() {
        return EntropykErrorCode::EntropykNullPointer;
    }

    let inner = &*(result as *const SolverResultInner);
    let actual_len = inner.state.len() as c_uint;

    if out.is_null() {
        *len = actual_len;
        return EntropykErrorCode::EntropykOk;
    }

    if *len < actual_len {
        *len = actual_len;
        return EntropykErrorCode::EntropykInvalidArgument;
    }

    std::ptr::copy_nonoverlapping(inner.state.as_ptr(), out, actual_len as usize);
    *len = actual_len;
    EntropykErrorCode::EntropykOk
}

/// Free a solver result.
///
/// # Safety
///
/// - `result` must be a valid pointer from a solve function, or null
/// - After this call, `result` is invalid and must not be used
#[no_mangle]
pub unsafe extern "C" fn entropyk_result_free(result: *mut EntropykSolverResult) {
    if !result.is_null() {
        let _ = Box::from_raw(result as *mut SolverResultInner);
    }
}
