use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use crate::config::Config;
use crate::engine::key_stats::KeyStatsStore;
use crate::engine::skill_tree::SkillTreeProgress;
use crate::session::result::DrillResult;

pub const SCHEMA_VERSION: u32 = 4;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProfileData {
    pub schema_version: u32,
    /// Legacy single-scope progress mirror retained for import/export compatibility.
    /// Always write this via `set_skill_tree_for_language`, never directly.
    pub skill_tree: SkillTreeProgress,
    /// Language-scoped skill tree progression state keyed by dictionary language.
    #[serde(default)]
    pub skill_tree_by_language: HashMap<String, SkillTreeProgress>,
    pub total_score: f64,
    #[serde(alias = "total_lessons")]
    pub total_drills: u32,
    pub streak_days: u32,
    pub best_streak: u32,
    pub last_practice_date: Option<String>,
}

impl Default for ProfileData {
    fn default() -> Self {
        Self {
            schema_version: SCHEMA_VERSION,
            skill_tree: SkillTreeProgress::default(),
            skill_tree_by_language: HashMap::new(),
            total_score: 0.0,
            total_drills: 0,
            streak_days: 0,
            best_streak: 0,
            last_practice_date: None,
        }
    }
}

impl ProfileData {
    /// Check if loaded data has a stale schema version and needs reset.
    pub fn needs_reset(&self) -> bool {
        self.schema_version != SCHEMA_VERSION
    }

    pub fn skill_tree_for_language(&self, language_key: &str) -> SkillTreeProgress {
        self.skill_tree_by_language
            .get(language_key)
            .cloned()
            .unwrap_or_else(|| self.skill_tree.clone())
    }

    pub fn set_skill_tree_for_language(&mut self, language_key: &str, progress: SkillTreeProgress) {
        self.skill_tree_by_language
            .insert(language_key.to_string(), progress.clone());
        // Keep legacy mirror aligned with the current active scope.
        self.skill_tree = progress;
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct KeyStatsData {
    pub schema_version: u32,
    pub stats: KeyStatsStore,
}

impl Default for KeyStatsData {
    fn default() -> Self {
        Self {
            schema_version: SCHEMA_VERSION,
            stats: KeyStatsStore::default(),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct DrillHistoryData {
    pub schema_version: u32,
    #[serde(alias = "lessons")]
    pub drills: Vec<DrillResult>,
}

impl Default for DrillHistoryData {
    fn default() -> Self {
        Self {
            schema_version: SCHEMA_VERSION,
            drills: Vec::new(),
        }
    }
}

pub const EXPORT_VERSION: u32 = 1;

/// Export contract: drill_history is the sole source of truth for n-gram stats.
/// N-gram data is always rebuilt from history on import/startup, so it is not
/// included in the export payload.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExportData {
    pub keydr_export_version: u32,
    pub exported_at: DateTime<Utc>,
    pub config: Config,
    pub profile: ProfileData,
    pub key_stats: KeyStatsData,
    pub ranked_key_stats: KeyStatsData,
    pub drill_history: DrillHistoryData,
}

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

    #[test]
    fn profile_skill_tree_for_language_falls_back_to_legacy() {
        let profile = ProfileData::default();
        let scoped = profile.skill_tree_for_language("de");
        let lowercase = scoped
            .branches
            .get("lowercase")
            .expect("lowercase branch should exist");
        assert_eq!(lowercase.current_level, 0);
    }

    #[test]
    fn profile_set_skill_tree_for_language_updates_scoped_map() {
        let mut profile = ProfileData::default();
        let mut progress = SkillTreeProgress::default();
        progress
            .branches
            .get_mut("lowercase")
            .expect("lowercase branch should exist")
            .current_level = 3;
        profile.set_skill_tree_for_language("de", progress.clone());

        let loaded = profile.skill_tree_for_language("de");
        assert_eq!(
            loaded
                .branches
                .get("lowercase")
                .expect("lowercase branch should exist")
                .current_level,
            3
        );
        assert_eq!(
            profile
                .skill_tree
                .branches
                .get("lowercase")
                .expect("lowercase branch should exist")
                .current_level,
            3
        );
    }
}
