//! Experiment directory setup and management.
//!
//! Provides utilities for creating and managing experiment directories
//! with consistent structure for tuning runs.

use std::fs::{self, File};
use std::io::{self, Write as _};
use std::path::PathBuf;

use crate::bots::{BotWeights, GreedyWeights, WeightSet};
use crate::version::VersionInfo;

/// Configuration for an experiment.
#[derive(Clone, Debug)]
pub struct ExperimentConfig {
    /// Base directory for experiments (e.g., "experiments").
    pub base_dir: PathBuf,
    /// Subdirectory category (e.g., "mcts").
    pub category: String,
    /// Descriptive tag for this experiment.
    pub tag: String,
}

impl ExperimentConfig {
    /// Create a new experiment config.
    pub fn new(base_dir: impl Into<PathBuf>, category: impl Into<String>, tag: impl Into<String>) -> Self {
        Self {
            base_dir: base_dir.into(),
            category: category.into(),
            tag: tag.into(),
        }
    }
}

/// Represents a created experiment directory.
pub struct ExperimentDir {
    /// Root experiment directory path.
    pub root: PathBuf,
    /// Plots subdirectory path.
    pub plots_dir: PathBuf,
    /// Experiment ID (timestamp_tag).
    pub id: String,
}

impl ExperimentDir {
    /// Create a new experiment directory with timestamped ID.
    ///
    /// Creates the directory structure:
    /// ```text
    /// {base_dir}/{category}/{timestamp}_{tag}/
    /// └── plots/
    /// ```
    pub fn create(config: &ExperimentConfig) -> io::Result<Self> {
        let timestamp = chrono::Local::now().format("%Y-%m-%d_%H%M").to_string();
        let id = format!("{}_{}", timestamp, config.tag);
        let root = config.base_dir.join(&config.category).join(&id);

        fs::create_dir_all(&root)?;

        let plots_dir = root.join("plots");
        fs::create_dir_all(&plots_dir)?;

        Ok(Self {
            root,
            plots_dir,
            id,
        })
    }

    /// Create an ExperimentDir from an existing directory path.
    ///
    /// Used when resuming from a checkpoint.
    pub fn from_existing(path: &std::path::Path) -> Self {
        let id = path
            .file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("unknown")
            .to_string();

        Self {
            root: path.to_path_buf(),
            plots_dir: path.join("plots"),
            id,
        }
    }

    /// Save version info for reproducibility.
    pub fn save_version(&self) -> io::Result<()> {
        let version_info = VersionInfo::current();
        let version_path = self.root.join("version.toml");
        let toml_str = toml::to_string_pretty(&version_info)
            .map_err(io::Error::other)?;
        fs::write(version_path, toml_str)
    }

    /// Save weights to the experiment directory.
    pub fn save_weights(&self, weights: &GreedyWeights, name: &str) -> io::Result<PathBuf> {
        let weights_path = self.root.join("weights.toml");
        let bot_weights = BotWeights {
            name: name.to_string(),
            version: 1,
            default: WeightSet {
                greedy: weights.clone(),
            },
            deck_specific: std::collections::HashMap::new(),
        };

        bot_weights
            .save(&weights_path)
            .map_err(io::Error::other)?;

        Ok(weights_path)
    }

    /// Save summary metadata for the experiment.
    pub fn save_summary(
        &self,
        mode: &str,
        best_fitness: f64,
        best_win_rate: f64,
        total_time_secs: f64,
        generations: u32,
    ) -> io::Result<()> {
        let summary_path = self.root.join("summary.txt");
        let mut summary_file = File::create(summary_path)?;
        writeln!(summary_file, "Experiment: {}", self.id)?;
        writeln!(summary_file, "Mode: {}", mode)?;
        writeln!(summary_file, "Best Fitness: {:.2}", best_fitness)?;
        writeln!(summary_file, "Best Win Rate: {:.1}%", best_win_rate * 100.0)?;
        writeln!(summary_file, "Total Time: {:.1}s", total_time_secs)?;
        writeln!(summary_file, "Generations: {}", generations)?;
        Ok(())
    }
}

/// Auto-deploy weights to the canonical location in data/weights/.
///
/// Determines the deploy path based on mode and faction/archetype:
/// - generalist -> data/weights/generalist.toml
/// - archetype -> data/weights/archetypes/{archetype}.toml
/// - faction-specialist -> data/weights/specialists/{faction}.toml (deprecated)
/// - specialist -> data/weights/specialists/{faction}.toml (inferred from deck name)
/// - alphabeta -> data/weights/alphabeta/generalist.toml
/// - alphabeta-specialist -> data/weights/alphabeta/specialists/{faction}.toml
pub fn deploy_weights(
    weights: &GreedyWeights,
    name: &str,
    mode: &str,
    faction_or_archetype: Option<&str>,
    deck: Option<&str>,
) -> io::Result<Option<PathBuf>> {
    let deploy_path = match mode {
        "generalist" | "agent-generalist" => Some(PathBuf::from("data/weights/generalist.toml")),
        "archetype" => {
            if let Some(archetype_str) = faction_or_archetype {
                let archetypes_dir = PathBuf::from("data/weights/archetypes");
                fs::create_dir_all(&archetypes_dir)?;
                Some(archetypes_dir.join(format!("{}.toml", archetype_str.to_lowercase())))
            } else {
                None
            }
        }
        "alphabeta" => {
            let alphabeta_dir = PathBuf::from("data/weights/alphabeta");
            fs::create_dir_all(&alphabeta_dir)?;
            Some(alphabeta_dir.join("generalist.toml"))
        }
        "alphabeta-specialist" => {
            if let Some(faction_str) = faction_or_archetype {
                let alphabeta_specialists_dir = PathBuf::from("data/weights/alphabeta/specialists");
                fs::create_dir_all(&alphabeta_specialists_dir)?;
                Some(alphabeta_specialists_dir.join(format!("{}.toml", faction_str.to_lowercase())))
            } else {
                None
            }
        }
        "faction-specialist" => {
            if let Some(faction_str) = faction_or_archetype {
                let specialists_dir = PathBuf::from("data/weights/specialists");
                fs::create_dir_all(&specialists_dir)?;
                Some(specialists_dir.join(format!("{}.toml", faction_str.to_lowercase())))
            } else {
                None
            }
        }
        "specialist" => {
            if let Some(deck_name) = deck {
                let specialists_dir = PathBuf::from("data/weights/specialists");
                fs::create_dir_all(&specialists_dir)?;
                // Extract prefix from deck name (e.g., "architect_fortify" -> "architect")
                let faction = deck_name.split('_').next().unwrap_or(deck_name);
                Some(specialists_dir.join(format!("{}.toml", faction.to_lowercase())))
            } else {
                None
            }
        }
        _ => None,
    };

    if let Some(ref path) = deploy_path {
        let bot_weights = BotWeights {
            name: name.to_string(),
            version: 1,
            default: WeightSet {
                greedy: weights.clone(),
            },
            deck_specific: std::collections::HashMap::new(),
        };

        bot_weights
            .save(path)
            .map_err(io::Error::other)?;
    }

    Ok(deploy_path)
}
