//! YAML loading and validation for cards and commanders.

use std::fs;
use std::path::{Path, PathBuf};
use thiserror::Error;
use super::card_types::{CardDefinition, CardSet, CardType, EffectDefinition};
use super::commander_types::{CommanderDefinition, CommanderSet};
use super::database::CardDatabase;

// =============================================================================
// Error Type
// =============================================================================

/// Errors that can occur when loading cards
#[derive(Error, Debug)]
pub enum CardLoadError {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    #[error("YAML parse error: {0}")]
    Yaml(#[from] serde_yaml::Error),
    #[error("Card validation error: {0}")]
    Validation(String),
}

// =============================================================================
// CardDatabase loading impl
// =============================================================================

impl CardDatabase {
    /// Validate an effect definition for problematic values
    fn validate_effect_definition(card: &CardDefinition, effect: &EffectDefinition) -> Result<(), CardLoadError> {
        match effect {
            EffectDefinition::BuffStats { attack, health, .. } |
            EffectDefinition::BuffStatsTemporal { attack, health } => {
                if attack.abs() > 20 {
                    return Err(CardLoadError::Validation(format!(
                        "Card {} '{}' has extreme attack buff: {} (abs max 20)",
                        card.id, card.name, attack
                    )));
                }
                if health.abs() > 20 {
                    return Err(CardLoadError::Validation(format!(
                        "Card {} '{}' has extreme health buff: {} (abs max 20)",
                        card.id, card.name, health
                    )));
                }
            },
            EffectDefinition::Transform { into, .. } => {
                if into.health == 0 {
                    return Err(CardLoadError::Validation(format!(
                        "Card {} '{}' has Transform effect with invalid health: 0 (must be > 0)",
                        card.id, card.name
                    )));
                }
                if into.health > 50 {
                    return Err(CardLoadError::Validation(format!(
                        "Card {} '{}' has Transform effect with extreme health: {} (max 50)",
                        card.id, card.name, into.health
                    )));
                }
                if into.attack > 50 {
                    return Err(CardLoadError::Validation(format!(
                        "Card {} '{}' has Transform effect with extreme attack: {} (max 50)",
                        card.id, card.name, into.attack
                    )));
                }
            },
            EffectDefinition::SummonToken { token } => {
                if token.health == 0 {
                    return Err(CardLoadError::Validation(format!(
                        "Card {} '{}' has SummonToken effect with invalid health: 0 (must be > 0)",
                        card.id, card.name
                    )));
                }
                if token.health > 50 {
                    return Err(CardLoadError::Validation(format!(
                        "Card {} '{}' has SummonToken effect with extreme health: {} (max 50)",
                        card.id, card.name, token.health
                    )));
                }
                if token.attack > 50 {
                    return Err(CardLoadError::Validation(format!(
                        "Card {} '{}' has SummonToken effect with extreme attack: {} (max 50)",
                        card.id, card.name, token.attack
                    )));
                }
            },
            // Copy defers validation to source creature; other types have no dangerous values.
            _ => {}
        }
        Ok(())
    }

    /// Load cards from a directory containing YAML files.
    ///
    /// The directory should contain `.yaml` or `.yml` files with card definitions.
    /// For example, if your cards are in `data/cards/core_set/`, pass that full path.
    ///
    /// # Errors
    /// Returns an error if:
    /// - The directory doesn't exist
    /// - No cards are found (empty directory or no valid YAML files)
    /// - Duplicate card IDs are found
    /// - YAML parsing fails
    pub fn load_from_directory<P: AsRef<Path>>(path: P) -> Result<Self, CardLoadError> {
        let dir_path = path.as_ref();

        if !dir_path.exists() {
            return Err(CardLoadError::Validation(format!(
                "Card directory does not exist: {}",
                dir_path.display()
            )));
        }

        if !dir_path.is_dir() {
            return Err(CardLoadError::Validation(format!(
                "Path is not a directory: {}",
                dir_path.display()
            )));
        }

        let mut all_cards = Vec::new();

        // Recursively collect all YAML files from the directory and subdirectories
        fn collect_yaml_files(dir: &Path, files: &mut Vec<PathBuf>) -> std::io::Result<()> {
            for entry in fs::read_dir(dir)? {
                let entry = entry?;
                let path = entry.path();
                if path.is_dir() {
                    collect_yaml_files(&path, files)?;
                } else if path.extension().is_some_and(|ext| ext == "yaml" || ext == "yml") {
                    files.push(path);
                }
            }
            Ok(())
        }

        let mut yaml_files = Vec::new();
        collect_yaml_files(dir_path, &mut yaml_files)?;

        // Sort for deterministic loading order
        yaml_files.sort();

        for file_path in yaml_files {
            let yaml_content = fs::read_to_string(&file_path)?;
            let card_set: CardSet = serde_yaml::from_str(&yaml_content)?;
            all_cards.extend(card_set.cards);
        }

        if all_cards.is_empty() {
            return Err(CardLoadError::Validation(format!(
                "No cards found in directory: {}. Expected .yaml or .yml files with card definitions.",
                dir_path.display()
            )));
        }

        // Validate no duplicate IDs
        let mut seen_ids = std::collections::HashSet::new();
        for card in &all_cards {
            if !seen_ids.insert(card.id) {
                return Err(CardLoadError::Validation(format!("Duplicate card ID: {}", card.id)));
            }
        }

        // Validate card definitions for problematic values
        for card in &all_cards {
            match &card.card_type {
                CardType::Creature { attack, health, abilities, .. } => {
                    if *health == 0 {
                        return Err(CardLoadError::Validation(format!(
                            "Card {} '{}' has invalid base health: 0 (must be > 0)",
                            card.id, card.name
                        )));
                    }
                    if *health > 50 {
                        return Err(CardLoadError::Validation(format!(
                            "Card {} '{}' has suspiciously high health: {} (max 50)",
                            card.id, card.name, health
                        )));
                    }
                    if *attack > 50 {
                        return Err(CardLoadError::Validation(format!(
                            "Card {} '{}' has suspiciously high attack: {} (max 50)",
                            card.id, card.name, attack
                        )));
                    }
                    for ability in abilities {
                        for effect in &ability.effects {
                            Self::validate_effect_definition(card, effect)?;
                        }
                        for group in &ability.conditional_effects {
                            for effect in &group.effects {
                                Self::validate_effect_definition(card, effect)?;
                            }
                        }
                    }
                },
                CardType::Spell { effects, conditional_effects, .. } => {
                    for effect in effects {
                        Self::validate_effect_definition(card, effect)?;
                    }
                    for group in conditional_effects {
                        for effect in &group.effects {
                            Self::validate_effect_definition(card, effect)?;
                        }
                    }
                },
                CardType::Support { triggered_effects, .. } => {
                    for ability in triggered_effects {
                        for effect in &ability.effects {
                            Self::validate_effect_definition(card, effect)?;
                        }
                        for group in &ability.conditional_effects {
                            for effect in &group.effects {
                                Self::validate_effect_definition(card, effect)?;
                            }
                        }
                    }
                },
                CardType::Ritual { effects, conditional_effects, .. } => {
                    for effect in effects {
                        Self::validate_effect_definition(card, effect)?;
                    }
                    for group in conditional_effects {
                        for effect in &group.effects {
                            Self::validate_effect_definition(card, effect)?;
                        }
                    }
                },
            }
        }

        Ok(Self::new(all_cards))
    }

    /// Load cards from a single YAML string (useful for testing)
    pub fn load_from_yaml(yaml: &str) -> Result<Self, CardLoadError> {
        let card_set: CardSet = serde_yaml::from_str(yaml)?;

        let mut seen_ids = std::collections::HashSet::new();
        for card in &card_set.cards {
            if !seen_ids.insert(card.id) {
                return Err(CardLoadError::Validation(format!("Duplicate card ID: {}", card.id)));
            }
        }

        Ok(Self::new(card_set.cards))
    }

    /// Load commanders from a directory containing YAML files.
    ///
    /// The directory should contain `.yaml` or `.yml` files with commander definitions.
    /// For example, if your commanders are in `data/commanders/`, pass that full path.
    ///
    /// # Returns
    /// A vector of commander definitions (not a CardDatabase - use `with_commanders` to add them).
    ///
    /// # Errors
    /// Returns an error if:
    /// - The directory doesn't exist
    /// - Duplicate commander IDs are found
    /// - YAML parsing fails
    pub fn load_commanders_from_directory<P: AsRef<Path>>(path: P) -> Result<Vec<CommanderDefinition>, CardLoadError> {
        let dir_path = path.as_ref();

        if !dir_path.exists() {
            return Err(CardLoadError::Validation(format!(
                "Commander directory does not exist: {}",
                dir_path.display()
            )));
        }

        if !dir_path.is_dir() {
            return Err(CardLoadError::Validation(format!(
                "Path is not a directory: {}",
                dir_path.display()
            )));
        }

        let mut all_commanders = Vec::new();

        for entry in fs::read_dir(dir_path)? {
            let entry = entry?;
            let file_path = entry.path();
            if file_path.extension().is_some_and(|ext| ext == "yaml" || ext == "yml") {
                let yaml_content = fs::read_to_string(&file_path)?;
                let commander_set: CommanderSet = serde_yaml::from_str(&yaml_content)?;
                all_commanders.extend(commander_set.commanders);
            }
        }

        let mut seen_ids = std::collections::HashSet::new();
        for commander in &all_commanders {
            if !seen_ids.insert(commander.id) {
                return Err(CardLoadError::Validation(format!(
                    "Duplicate commander ID: {}",
                    commander.id
                )));
            }
        }

        Ok(all_commanders)
    }

    /// Add commanders to this database, returning a new database with both cards and commanders.
    ///
    /// # Example
    /// ```ignore
    /// let card_db = CardDatabase::load_from_directory("data/cards/core_set")?;
    /// let commanders = CardDatabase::load_commanders_from_directory("data/commanders")?;
    /// let full_db = card_db.with_commanders(commanders);
    /// ```
    pub fn with_commanders(self, commanders: Vec<CommanderDefinition>) -> Self {
        let cards: Vec<CardDefinition> = (*self.cards).clone();
        Self::new_with_commanders(cards, commanders)
    }

    /// Load both cards and commanders from their respective directories.
    ///
    /// This is a convenience method that combines `load_from_directory` and
    /// `load_commanders_from_directory`.
    ///
    /// # Arguments
    /// * `cards_path` - Path to card definitions (e.g., "data/cards/core_set")
    /// * `commanders_path` - Path to commander definitions (e.g., "data/commanders")
    ///
    /// # Errors
    /// Returns an error if either directory fails to load.
    pub fn load_with_commanders<P1: AsRef<Path>, P2: AsRef<Path>>(
        cards_path: P1,
        commanders_path: P2,
    ) -> Result<Self, CardLoadError> {
        let card_db = Self::load_from_directory(cards_path)?;
        let commanders = Self::load_commanders_from_directory(commanders_path)?;
        Ok(card_db.with_commanders(commanders))
    }

    /// Load commanders from a single YAML string (useful for testing)
    pub fn load_commanders_from_yaml(yaml: &str) -> Result<Vec<CommanderDefinition>, CardLoadError> {
        let commander_set: CommanderSet = serde_yaml::from_str(yaml)?;

        let mut seen_ids = std::collections::HashSet::new();
        for commander in &commander_set.commanders {
            if !seen_ids.insert(commander.id) {
                return Err(CardLoadError::Validation(format!(
                    "Duplicate commander ID: {}",
                    commander.id
                )));
            }
        }

        Ok(commander_set.commanders)
    }
}
