use arma_rs::{FromArma, IntoArma};
use forge_shared::GarageValidationError;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Garage {
    pub vehicles: HashMap<String, Vehicle>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Vehicle {
    pub plate: String,
    pub classname: String,
    pub fuel: f64,
    pub damage: f64,
    pub hit_points: HitPoints,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HitPoints {
    pub names: Vec<String>,
    pub selections: Vec<String>,
    pub values: Vec<f64>,
}

impl HitPoints {
    pub fn new() -> Self {
        Self {
            names: Vec::new(),
            selections: Vec::new(),
            values: Vec::new(),
        }
    }

    pub fn from_json_str(json_str: &str) -> Result<Self, String> {
        let hit_points: HitPoints = serde_json::from_str(json_str)
            .map_err(|e| format!("Failed to parse hit_points JSON: {}", e))?;

        let names_len = hit_points.names.len();
        let selections_len = hit_points.selections.len();
        let values_len = hit_points.values.len();

        if names_len != selections_len || names_len != values_len {
            return Err(format!(
                "Hitpoint array length mismatch: names={}, selections={}, values={}",
                names_len, selections_len, values_len
            ));
        }

        Ok(hit_points)
    }
}

impl Default for HitPoints {
    fn default() -> Self {
        Self::new()
    }
}

impl Vehicle {
    pub fn new<S: Into<String>>(
        plate: S,
        classname: S,
        fuel: f64,
        damage: f64,
        hit_points: HitPoints,
    ) -> Result<Self, GarageValidationError> {
        let vehicle = Self {
            plate: plate.into(),
            classname: classname.into(),
            fuel,
            damage,
            hit_points,
        };

        vehicle.validate()?;
        Ok(vehicle)
    }

    pub fn validate(&self) -> Result<(), GarageValidationError> {
        if self.classname.trim().is_empty() {
            return Err(GarageValidationError::ClassnameEmpty);
        }

        if self.fuel < 0.0 || self.fuel > 1.0 {
            return Err(GarageValidationError::FuelInvalid);
        }

        if self.damage < 0.0 || self.damage > 1.0 {
            return Err(GarageValidationError::DamageInvalid);
        }

        let names_len = self.hit_points.names.len();
        let selections_len = self.hit_points.selections.len();
        let values_len = self.hit_points.values.len();

        if names_len != selections_len || names_len != values_len {
            return Err(GarageValidationError::HitpointArrayLengthMismatch);
        }

        for (i, value) in self.hit_points.values.iter().enumerate() {
            if *value < 0.0 || *value > 1.0 {
                return Err(GarageValidationError::HitpointValueInvalid(i));
            }
        }

        Ok(())
    }
}

impl Garage {
    pub fn new() -> Result<Self, GarageValidationError> {
        let garage = Self {
            vehicles: HashMap::new(),
        };

        garage.validate()?;
        Ok(garage)
    }

    pub fn validate(&self) -> Result<(), GarageValidationError> {
        for vehicle in self.vehicles.values() {
            vehicle.validate()?;
        }

        Ok(())
    }

    pub fn add_vehicle(&mut self, vehicle: Vehicle) -> Result<(), GarageValidationError> {
        vehicle.validate()?;
        self.vehicles.insert(vehicle.plate.clone(), vehicle);
        Ok(())
    }

    pub fn remove_vehicle(&mut self, plate: &str) -> Option<Vehicle> {
        self.vehicles.remove(plate)
    }

    pub fn get_vehicle(&self, plate: &str) -> Option<&Vehicle> {
        self.vehicles.get(plate)
    }

    pub fn get_vehicle_mut(&mut self, plate: &str) -> Option<&mut Vehicle> {
        self.vehicles.get_mut(plate)
    }
}

impl FromArma for Vehicle {
    fn from_arma(s: String) -> Result<Self, arma_rs::FromArmaError> {
        serde_json::from_str(&s)
            .map_err(|e| arma_rs::FromArmaError::InvalidPrimitive(format!("Invalid JSON: {}", e)))
    }
}

impl IntoArma for Vehicle {
    fn to_arma(&self) -> arma_rs::Value {
        let json_str = serde_json::to_string(self).unwrap_or_default();
        arma_rs::Value::String(json_str)
    }
}
