//! Disaster event aggregate root.

use chrono::{DateTime, Utc};
use geo::Point;
use uuid::Uuid;

use super::{Coordinates3D, ScanZone, ScanZoneId, Survivor, SurvivorId, VitalSignsReading};
use crate::MatError;

/// Unique identifier for a disaster event
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DisasterEventId(Uuid);

impl DisasterEventId {
    /// Create a new random event ID
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    /// Get the inner UUID
    pub fn as_uuid(&self) -> &Uuid {
        &self.0
    }
}

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

impl std::fmt::Display for DisasterEventId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Types of disaster events
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DisasterType {
    /// Building collapse (explosion, structural failure)
    BuildingCollapse,
    /// Earthquake
    Earthquake,
    /// Landslide or mudslide
    Landslide,
    /// Avalanche (snow)
    Avalanche,
    /// Flood
    Flood,
    /// Mine collapse
    MineCollapse,
    /// Industrial accident
    Industrial,
    /// Tunnel collapse
    TunnelCollapse,
    /// Unknown or other
    Unknown,
}

impl DisasterType {
    /// Get typical debris profile for this disaster type
    pub fn typical_debris_profile(&self) -> super::DebrisProfile {
        use super::{DebrisMaterial, DebrisProfile, MetalContent, MoistureLevel};

        match self {
            DisasterType::BuildingCollapse => DebrisProfile {
                primary_material: DebrisMaterial::Mixed,
                void_fraction: 0.25,
                moisture_content: MoistureLevel::Dry,
                metal_content: MetalContent::Moderate,
            },
            DisasterType::Earthquake => DebrisProfile {
                primary_material: DebrisMaterial::HeavyConcrete,
                void_fraction: 0.2,
                moisture_content: MoistureLevel::Dry,
                metal_content: MetalContent::Moderate,
            },
            DisasterType::Avalanche => DebrisProfile {
                primary_material: DebrisMaterial::Snow,
                void_fraction: 0.4,
                moisture_content: MoistureLevel::Wet,
                metal_content: MetalContent::None,
            },
            DisasterType::Landslide => DebrisProfile {
                primary_material: DebrisMaterial::Soil,
                void_fraction: 0.15,
                moisture_content: MoistureLevel::Wet,
                metal_content: MetalContent::None,
            },
            DisasterType::Flood => DebrisProfile {
                primary_material: DebrisMaterial::Mixed,
                void_fraction: 0.3,
                moisture_content: MoistureLevel::Saturated,
                metal_content: MetalContent::Low,
            },
            DisasterType::MineCollapse | DisasterType::TunnelCollapse => DebrisProfile {
                primary_material: DebrisMaterial::Soil,
                void_fraction: 0.2,
                moisture_content: MoistureLevel::Damp,
                metal_content: MetalContent::Low,
            },
            DisasterType::Industrial => DebrisProfile {
                primary_material: DebrisMaterial::Metal,
                void_fraction: 0.35,
                moisture_content: MoistureLevel::Dry,
                metal_content: MetalContent::High,
            },
            DisasterType::Unknown => DebrisProfile::default(),
        }
    }

    /// Get expected maximum survival time (hours)
    pub fn expected_survival_hours(&self) -> u32 {
        match self {
            DisasterType::Avalanche => 2,     // Limited air, hypothermia
            DisasterType::Flood => 6,         // Drowning risk
            DisasterType::MineCollapse => 72, // Air supply critical
            DisasterType::BuildingCollapse => 96,
            DisasterType::Earthquake => 120,
            DisasterType::Landslide => 48,
            DisasterType::TunnelCollapse => 72,
            DisasterType::Industrial => 72,
            DisasterType::Unknown => 72,
        }
    }
}

impl Default for DisasterType {
    fn default() -> Self {
        Self::Unknown
    }
}

/// Current status of the disaster event
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EventStatus {
    /// Event just reported, setting up
    Initializing,
    /// Active search and rescue
    Active,
    /// Search suspended (weather, safety)
    Suspended,
    /// Primary rescue complete, secondary search
    SecondarySearch,
    /// Event closed
    Closed,
}

/// Aggregate root for a disaster event
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DisasterEvent {
    id: DisasterEventId,
    event_type: DisasterType,
    start_time: DateTime<Utc>,
    location: Point<f64>,
    description: String,
    scan_zones: Vec<ScanZone>,
    survivors: Vec<Survivor>,
    status: EventStatus,
    metadata: EventMetadata,
}

/// Additional metadata for a disaster event
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EventMetadata {
    /// Estimated number of people in area at time of disaster
    pub estimated_occupancy: Option<u32>,
    /// Known survivors (already rescued)
    pub confirmed_rescued: u32,
    /// Known fatalities
    pub confirmed_deceased: u32,
    /// Weather conditions
    pub weather: Option<String>,
    /// Lead agency
    pub lead_agency: Option<String>,
    /// Notes
    pub notes: Vec<String>,
}

impl DisasterEvent {
    /// Create a new disaster event
    pub fn new(event_type: DisasterType, location: Point<f64>, description: &str) -> Self {
        Self {
            id: DisasterEventId::new(),
            event_type,
            start_time: Utc::now(),
            location,
            description: description.to_string(),
            scan_zones: Vec::new(),
            survivors: Vec::new(),
            status: EventStatus::Initializing,
            metadata: EventMetadata::default(),
        }
    }

    /// Get the event ID
    pub fn id(&self) -> &DisasterEventId {
        &self.id
    }

    /// Get the event type
    pub fn event_type(&self) -> &DisasterType {
        &self.event_type
    }

    /// Get the start time
    pub fn start_time(&self) -> &DateTime<Utc> {
        &self.start_time
    }

    /// Get the location
    pub fn location(&self) -> &Point<f64> {
        &self.location
    }

    /// Get the description
    pub fn description(&self) -> &str {
        &self.description
    }

    /// Get the scan zones
    pub fn zones(&self) -> &[ScanZone] {
        &self.scan_zones
    }

    /// Get mutable scan zones
    pub fn zones_mut(&mut self) -> &mut [ScanZone] {
        &mut self.scan_zones
    }

    /// Get the survivors
    pub fn survivors(&self) -> Vec<&Survivor> {
        self.survivors.iter().collect()
    }

    /// Get mutable survivors
    pub fn survivors_mut(&mut self) -> &mut [Survivor] {
        &mut self.survivors
    }

    /// Get the current status
    pub fn status(&self) -> &EventStatus {
        &self.status
    }

    /// Get metadata
    pub fn metadata(&self) -> &EventMetadata {
        &self.metadata
    }

    /// Get mutable metadata
    pub fn metadata_mut(&mut self) -> &mut EventMetadata {
        &mut self.metadata
    }

    /// Add a scan zone
    pub fn add_zone(&mut self, zone: ScanZone) {
        self.scan_zones.push(zone);

        // Activate event if first zone
        if self.status == EventStatus::Initializing {
            self.status = EventStatus::Active;
        }
    }

    /// Remove a scan zone
    pub fn remove_zone(&mut self, zone_id: &ScanZoneId) {
        self.scan_zones.retain(|z| z.id() != zone_id);
    }

    /// Record a new detection
    pub fn record_detection(
        &mut self,
        zone_id: ScanZoneId,
        vitals: VitalSignsReading,
        location: Option<Coordinates3D>,
    ) -> Result<&Survivor, MatError> {
        // Check if this might be an existing survivor
        let existing_id = if let Some(loc) = &location {
            self.find_nearby_survivor(loc, 2.0).cloned()
        } else {
            None
        };

        if let Some(existing) = existing_id {
            // Update existing survivor
            let survivor = self
                .survivors
                .iter_mut()
                .find(|s| s.id() == &existing)
                .ok_or_else(|| MatError::Domain("Survivor not found".into()))?;
            survivor.update_vitals(vitals);
            if let Some(l) = location {
                survivor.update_location(l);
            }
            return Ok(survivor);
        }

        // Create new survivor
        let survivor = Survivor::new(zone_id, vitals, location);
        self.survivors.push(survivor);
        // Safe: we just pushed, so last() is always Some
        Ok(self
            .survivors
            .last()
            .expect("survivors is non-empty after push"))
    }

    /// Find a survivor near a location
    fn find_nearby_survivor(&self, location: &Coordinates3D, radius: f64) -> Option<&SurvivorId> {
        for survivor in &self.survivors {
            if let Some(loc) = survivor.location() {
                if loc.distance_to(location) < radius {
                    return Some(survivor.id());
                }
            }
        }
        None
    }

    /// Get survivor by ID
    pub fn get_survivor(&self, id: &SurvivorId) -> Option<&Survivor> {
        self.survivors.iter().find(|s| s.id() == id)
    }

    /// Get mutable survivor by ID
    pub fn get_survivor_mut(&mut self, id: &SurvivorId) -> Option<&mut Survivor> {
        self.survivors.iter_mut().find(|s| s.id() == id)
    }

    /// Get zone by ID
    pub fn get_zone(&self, id: &ScanZoneId) -> Option<&ScanZone> {
        self.scan_zones.iter().find(|z| z.id() == id)
    }

    /// Set event status
    pub fn set_status(&mut self, status: EventStatus) {
        self.status = status;
    }

    /// Suspend operations
    pub fn suspend(&mut self, reason: &str) {
        self.status = EventStatus::Suspended;
        self.metadata.notes.push(format!(
            "[{}] Suspended: {}",
            Utc::now().format("%Y-%m-%d %H:%M:%S"),
            reason
        ));
    }

    /// Resume operations
    pub fn resume(&mut self) {
        if self.status == EventStatus::Suspended {
            self.status = EventStatus::Active;
            self.metadata.notes.push(format!(
                "[{}] Resumed operations",
                Utc::now().format("%Y-%m-%d %H:%M:%S")
            ));
        }
    }

    /// Close the event
    pub fn close(&mut self) {
        self.status = EventStatus::Closed;
    }

    /// Get time since event started
    pub fn elapsed_time(&self) -> chrono::Duration {
        Utc::now() - self.start_time
    }

    /// Get count of survivors by triage status
    pub fn triage_counts(&self) -> TriageCounts {
        use super::TriageStatus;

        let mut counts = TriageCounts::default();
        for survivor in &self.survivors {
            match survivor.triage_status() {
                TriageStatus::Immediate => counts.immediate += 1,
                TriageStatus::Delayed => counts.delayed += 1,
                TriageStatus::Minor => counts.minor += 1,
                TriageStatus::Deceased => counts.deceased += 1,
                TriageStatus::Unknown => counts.unknown += 1,
            }
        }
        counts
    }
}

/// Triage status counts
#[derive(Debug, Clone, Default)]
pub struct TriageCounts {
    /// Immediate (Red)
    pub immediate: u32,
    /// Delayed (Yellow)
    pub delayed: u32,
    /// Minor (Green)
    pub minor: u32,
    /// Deceased (Black)
    pub deceased: u32,
    /// Unknown
    pub unknown: u32,
}

impl TriageCounts {
    /// Total count
    pub fn total(&self) -> u32 {
        self.immediate + self.delayed + self.minor + self.deceased + self.unknown
    }

    /// Count of living survivors
    pub fn living(&self) -> u32 {
        self.immediate + self.delayed + self.minor
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::{BreathingPattern, BreathingType, ConfidenceScore, ZoneBounds};

    fn create_test_vitals() -> VitalSignsReading {
        VitalSignsReading {
            breathing: Some(BreathingPattern {
                rate_bpm: 16.0,
                amplitude: 0.8,
                regularity: 0.9,
                pattern_type: BreathingType::Normal,
            }),
            heartbeat: None,
            movement: Default::default(),
            timestamp: Utc::now(),
            confidence: ConfidenceScore::new(0.8),
        }
    }

    #[test]
    fn test_event_creation() {
        let event = DisasterEvent::new(
            DisasterType::Earthquake,
            Point::new(-122.4194, 37.7749),
            "Test earthquake event",
        );

        assert!(matches!(event.event_type(), DisasterType::Earthquake));
        assert_eq!(event.status(), &EventStatus::Initializing);
    }

    #[test]
    fn test_add_zone_activates_event() {
        let mut event =
            DisasterEvent::new(DisasterType::BuildingCollapse, Point::new(0.0, 0.0), "Test");

        assert_eq!(event.status(), &EventStatus::Initializing);

        let zone = ScanZone::new("Zone A", ZoneBounds::rectangle(0.0, 0.0, 10.0, 10.0));
        event.add_zone(zone);

        assert_eq!(event.status(), &EventStatus::Active);
    }

    #[test]
    fn test_record_detection() {
        let mut event = DisasterEvent::new(DisasterType::Earthquake, Point::new(0.0, 0.0), "Test");

        let zone = ScanZone::new("Zone A", ZoneBounds::rectangle(0.0, 0.0, 10.0, 10.0));
        let zone_id = zone.id().clone();
        event.add_zone(zone);

        let vitals = create_test_vitals();
        event.record_detection(zone_id, vitals, None).unwrap();

        assert_eq!(event.survivors().len(), 1);
    }

    #[test]
    fn test_disaster_type_survival_hours() {
        assert!(
            DisasterType::Avalanche.expected_survival_hours()
                < DisasterType::Earthquake.expected_survival_hours()
        );
    }
}
