use std::collections::{HashMap, HashSet};
use std::fmt;

use bevy::prelude::*;
use serde::{Deserialize, Serialize};

pub const AUDIO_BUS_MASTER_ID: &str = "master";
pub const AUDIO_BUS_MUSIC_ID: &str = "music";
pub const AUDIO_BUS_SFX_ID: &str = "sfx";
pub const AUDIO_GAIN_DB_MIN: f32 = -80.0;
pub const AUDIO_GAIN_DB_MAX: f32 = 24.0;

/// One stable node in the project audio bus graph.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Reflect)]
#[reflect(Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct AudioBusSettings {
    /// Stable serialized ID. Display names may change without breaking source routing.
    #[serde(default)]
    pub id: String,
    #[serde(default)]
    pub label: String,
    #[serde(default)]
    pub parent: Option<String>,
    #[serde(default)]
    pub gain_db: f32,
    #[serde(default)]
    pub muted: bool,
}

impl AudioBusSettings {
    pub fn new(
        id: impl Into<String>,
        label: impl Into<String>,
        parent: Option<impl Into<String>>,
    ) -> Self {
        Self {
            id: id.into(),
            label: label.into(),
            parent: parent.map(Into::into),
            gain_db: 0.0,
            muted: false,
        }
    }
}

impl Default for AudioBusSettings {
    fn default() -> Self {
        Self::new(AUDIO_BUS_MASTER_ID, "Master", None::<String>)
    }
}

/// Versioned project audio routing settings.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Reflect)]
#[reflect(Default, Debug, PartialEq, Serialize, Deserialize)]
pub struct AudioSettings {
    #[serde(default = "default_audio_buses")]
    pub buses: Vec<AudioBusSettings>,
}

impl AudioSettings {
    pub fn bus(&self, id: &str) -> Option<&AudioBusSettings> {
        self.buses.iter().find(|bus| bus.id == id)
    }

    pub fn bus_mut(&mut self, id: &str) -> Option<&mut AudioBusSettings> {
        self.buses.iter_mut().find(|bus| bus.id == id)
    }

    pub fn validate(&self) -> Result<(), AudioSettingsValidationError> {
        validate_audio_settings(self)
    }
}

impl Default for AudioSettings {
    fn default() -> Self {
        Self {
            buses: default_audio_buses(),
        }
    }
}

fn default_audio_buses() -> Vec<AudioBusSettings> {
    vec![
        AudioBusSettings::default(),
        AudioBusSettings::new(AUDIO_BUS_MUSIC_ID, "Music", Some(AUDIO_BUS_MASTER_ID)),
        AudioBusSettings::new(AUDIO_BUS_SFX_ID, "SFX", Some(AUDIO_BUS_MASTER_ID)),
    ]
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AudioSettingsValidationError {
    EmptyBusId { index: usize },
    InvalidBusId { bus_id: String },
    EmptyBusLabel { bus_id: String },
    DuplicateBusId { bus_id: String },
    InvalidBusGain { bus_id: String },
    MissingRequiredBus { bus_id: &'static str },
    MasterBusHasParent,
    BusMissingParent { bus_id: String },
    UnknownParent { bus_id: String, parent_id: String },
    BusParentCycle { bus_id: String },
}

impl fmt::Display for AudioSettingsValidationError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::EmptyBusId { index } => write!(formatter, "audio bus {index} has an empty ID"),
            Self::InvalidBusId { bus_id } => write!(
                formatter,
                "audio bus ID `{bus_id}` must use lowercase ASCII letters, digits, '.', '-', or '_'"
            ),
            Self::EmptyBusLabel { bus_id } => {
                write!(formatter, "audio bus `{bus_id}` has an empty label")
            }
            Self::DuplicateBusId { bus_id } => {
                write!(formatter, "audio bus ID `{bus_id}` is duplicated")
            }
            Self::InvalidBusGain { bus_id } => write!(
                formatter,
                "audio bus `{bus_id}` gain must be finite and between {AUDIO_GAIN_DB_MIN} and {AUDIO_GAIN_DB_MAX} dB"
            ),
            Self::MissingRequiredBus { bus_id } => {
                write!(formatter, "required audio bus `{bus_id}` is missing")
            }
            Self::MasterBusHasParent => write!(formatter, "the master audio bus cannot have a parent"),
            Self::BusMissingParent { bus_id } => {
                write!(formatter, "audio bus `{bus_id}` does not route to a parent")
            }
            Self::UnknownParent { bus_id, parent_id } => write!(
                formatter,
                "audio bus `{bus_id}` references unknown parent `{parent_id}`"
            ),
            Self::BusParentCycle { bus_id } => {
                write!(formatter, "audio bus `{bus_id}` participates in a parent cycle")
            }
        }
    }
}

impl std::error::Error for AudioSettingsValidationError {}

/// Validates stable IDs, required buses, scalar bounds, and the parent graph.
pub fn validate_audio_settings(
    settings: &AudioSettings,
) -> Result<(), AudioSettingsValidationError> {
    let mut buses = HashMap::with_capacity(settings.buses.len());
    for (index, bus) in settings.buses.iter().enumerate() {
        if bus.id.is_empty() {
            return Err(AudioSettingsValidationError::EmptyBusId { index });
        }
        if !valid_bus_id(&bus.id) {
            return Err(AudioSettingsValidationError::InvalidBusId {
                bus_id: bus.id.clone(),
            });
        }
        if bus.label.trim().is_empty() {
            return Err(AudioSettingsValidationError::EmptyBusLabel {
                bus_id: bus.id.clone(),
            });
        }
        if !bus.gain_db.is_finite()
            || !(AUDIO_GAIN_DB_MIN..=AUDIO_GAIN_DB_MAX).contains(&bus.gain_db)
        {
            return Err(AudioSettingsValidationError::InvalidBusGain {
                bus_id: bus.id.clone(),
            });
        }
        if buses.insert(bus.id.as_str(), bus).is_some() {
            return Err(AudioSettingsValidationError::DuplicateBusId {
                bus_id: bus.id.clone(),
            });
        }
    }

    for required in [AUDIO_BUS_MASTER_ID, AUDIO_BUS_MUSIC_ID, AUDIO_BUS_SFX_ID] {
        if !buses.contains_key(required) {
            return Err(AudioSettingsValidationError::MissingRequiredBus { bus_id: required });
        }
    }

    if buses[AUDIO_BUS_MASTER_ID].parent.is_some() {
        return Err(AudioSettingsValidationError::MasterBusHasParent);
    }

    for bus in settings
        .buses
        .iter()
        .filter(|bus| bus.id != AUDIO_BUS_MASTER_ID)
    {
        let Some(parent_id) = bus.parent.as_deref().filter(|parent| !parent.is_empty()) else {
            return Err(AudioSettingsValidationError::BusMissingParent {
                bus_id: bus.id.clone(),
            });
        };
        if !buses.contains_key(parent_id) {
            return Err(AudioSettingsValidationError::UnknownParent {
                bus_id: bus.id.clone(),
                parent_id: parent_id.to_string(),
            });
        }
    }

    for bus in settings
        .buses
        .iter()
        .filter(|bus| bus.id != AUDIO_BUS_MASTER_ID)
    {
        let mut current = bus.id.as_str();
        let mut visited = HashSet::new();
        while current != AUDIO_BUS_MASTER_ID {
            if !visited.insert(current) {
                return Err(AudioSettingsValidationError::BusParentCycle {
                    bus_id: bus.id.clone(),
                });
            }
            current = buses[current]
                .parent
                .as_deref()
                .expect("non-master parents were validated above");
        }
    }

    Ok(())
}

fn valid_bus_id(id: &str) -> bool {
    id.bytes().all(|byte| {
        byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'-' | b'_')
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn default_bus_graph_uses_stable_required_ids() {
        let settings = AudioSettings::default();

        assert_eq!(
            settings
                .buses
                .iter()
                .map(|bus| bus.id.as_str())
                .collect::<Vec<_>>(),
            [AUDIO_BUS_MASTER_ID, AUDIO_BUS_MUSIC_ID, AUDIO_BUS_SFX_ID]
        );
        assert_eq!(
            settings.bus(AUDIO_BUS_MUSIC_ID).unwrap().parent.as_deref(),
            Some(AUDIO_BUS_MASTER_ID)
        );
        assert_eq!(
            settings.bus(AUDIO_BUS_SFX_ID).unwrap().parent.as_deref(),
            Some(AUDIO_BUS_MASTER_ID)
        );
        assert!(settings.validate().is_ok());
    }

    #[test]
    fn rejects_duplicate_and_unknown_bus_references() {
        let mut settings = AudioSettings::default();
        settings.buses.push(AudioBusSettings::new(
            AUDIO_BUS_SFX_ID,
            "Duplicate",
            Some(AUDIO_BUS_MASTER_ID),
        ));
        assert_eq!(
            settings.validate(),
            Err(AudioSettingsValidationError::DuplicateBusId {
                bus_id: AUDIO_BUS_SFX_ID.to_string()
            })
        );

        let mut settings = AudioSettings::default();
        settings.bus_mut(AUDIO_BUS_SFX_ID).unwrap().parent = Some("missing".into());
        assert_eq!(
            settings.validate(),
            Err(AudioSettingsValidationError::UnknownParent {
                bus_id: AUDIO_BUS_SFX_ID.to_string(),
                parent_id: "missing".to_string(),
            })
        );
    }

    #[test]
    fn rejects_parent_cycles_and_invalid_gain() {
        let mut settings = AudioSettings::default();
        settings.bus_mut(AUDIO_BUS_MUSIC_ID).unwrap().parent = Some(AUDIO_BUS_SFX_ID.into());
        settings.bus_mut(AUDIO_BUS_SFX_ID).unwrap().parent = Some(AUDIO_BUS_MUSIC_ID.into());
        assert_eq!(
            settings.validate(),
            Err(AudioSettingsValidationError::BusParentCycle {
                bus_id: AUDIO_BUS_MUSIC_ID.to_string()
            })
        );

        let mut settings = AudioSettings::default();
        settings.bus_mut(AUDIO_BUS_SFX_ID).unwrap().gain_db = f32::NAN;
        assert_eq!(
            settings.validate(),
            Err(AudioSettingsValidationError::InvalidBusGain {
                bus_id: AUDIO_BUS_SFX_ID.to_string()
            })
        );
    }

    #[test]
    fn rejects_missing_required_and_unstable_bus_ids() {
        let mut settings = AudioSettings::default();
        settings.buses.retain(|bus| bus.id != AUDIO_BUS_MUSIC_ID);
        assert_eq!(
            settings.validate(),
            Err(AudioSettingsValidationError::MissingRequiredBus {
                bus_id: AUDIO_BUS_MUSIC_ID
            })
        );

        let mut settings = AudioSettings::default();
        settings.buses.push(AudioBusSettings::new(
            "Dialogue Bus",
            "Dialogue",
            Some(AUDIO_BUS_MASTER_ID),
        ));
        assert_eq!(
            settings.validate(),
            Err(AudioSettingsValidationError::InvalidBusId {
                bus_id: "Dialogue Bus".to_string()
            })
        );
    }
}
