use chrono::{Datelike, Local};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use std::time::SystemTime;

use crate::models::{DailyHourly, MultiTrendCache, Record, TrendCache};

/// 将值序列化为 pretty JSON 字符串，失败时返回 `io::Error` 而非静默写入空文件。
fn serialize_json<T: Serialize>(value: &T) -> io::Result<String> {
    serde_json::to_string_pretty(value).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}

/// 原子写入：先写临时文件，再 rename 覆盖目标文件，防止写入中途崩溃导致数据损坏。
fn atomic_write(path: &Path, content: &str) -> io::Result<()> {
    let tmp = path.with_extension("tmp");
    fs::write(&tmp, content)?;
    if let Err(e) = fs::rename(&tmp, path) {
        let _ = fs::remove_file(&tmp);
        return Err(e);
    }
    Ok(())
}

/// 历史摘要缓存结构。
#[derive(Serialize, Deserialize)]
pub struct HistoryCache {
    pub last_sync_time: String,
    pub summary: Record,
}

/// 崩溃恢复快照 V2 格式（JSON），兼容旧版 CSV。
#[derive(Serialize, Deserialize)]
struct QuickSaveV2 {
    date: String,
    record: Record,
}

/// 默认数据目录：~/.tapstat
pub fn default_data_dir() -> PathBuf {
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".into());
    PathBuf::from(home).join(".tapstat")
}

/// 返回桌面目录路径，跨平台支持 macOS/Linux/Windows。
pub fn desktop_dir() -> PathBuf {
    #[cfg(target_os = "windows")]
    {
        if let Ok(profile) = std::env::var("USERPROFILE") {
            return PathBuf::from(profile).join("Desktop");
        }
    }
    #[cfg(not(target_os = "windows"))]
    {
        if let Ok(home) = std::env::var("HOME") {
            return PathBuf::from(home).join("Desktop");
        }
    }
    PathBuf::from(".")
}

/// 向后兼容：旧数据中合并的 modifier（如 "Shift"）拆成左右各一半。
/// 仅在目标不存在左右键时才执行，防止重复拆分。
pub fn split_modifier_counts(counts: &mut HashMap<String, u64>) {
    for (old, left, right) in [
        ("Shift", "ShiftLeft", "ShiftRight"),
        ("Ctrl", "CtrlLeft", "CtrlRight"),
        ("Alt", "AltLeft", "AltRight"),
        ("Cmd", "CmdLeft", "CmdRight"),
    ] {
        if !counts.contains_key(left) && counts.contains_key(old) {
            let v = counts.remove(old).unwrap_or(0);
            counts.insert(left.to_string(), v / 2);
            counts.insert(right.to_string(), v - v / 2);
        }
    }
}

/// 将 source 中的所有计数累加到 target 中。
pub fn merge_record_into(target: &mut Record, source: &Record) {
    target.total_keys += source.total_keys;
    target.total_mouse += source.total_mouse;
    target.peak_kps = target.peak_kps.max(source.peak_kps);
    target.peak_cps = target.peak_cps.max(source.peak_cps);

    let mut source_counts = source.key_counts.clone();
    split_modifier_counts(&mut source_counts);

    for (k, v) in &source_counts {
        *target.key_counts.entry(k.clone()).or_insert(0) += *v;
    }
    for (k, v) in &source.mouse_counts {
        *target.mouse_counts.entry(k.clone()).or_insert(0) += *v;
    }
    target.backspace_count += source.backspace_count;
    target.delete_count += source.delete_count;
}

/// 严格判断文件名是否为有效日期 `YYYY-MM-DD.json`。
fn is_date_json(name: &str) -> bool {
    let stem = name.strip_suffix(".json").unwrap_or(name);
    // 先检查严格格式（10 字符，固定位置为 - 和数字）
    if stem.len() != 10
        || stem.chars().enumerate().any(|(i, c)| match i {
            4 | 7 => c != '-',
            _ => !c.is_ascii_digit(),
        })
    {
        return false;
    }
    // 再验证日期语义合法性
    chrono::NaiveDate::parse_from_str(stem, "%Y-%m-%d").is_ok()
}

/// 存储层封装。所有文件 IO 通过此结构体进行，支持自定义数据目录。
pub struct Store {
    data_dir: PathBuf,
}

impl Store {
    pub fn new<P: AsRef<Path>>(data_dir: P) -> Self {
        Self {
            data_dir: data_dir.as_ref().to_path_buf(),
        }
    }

    pub fn default() -> Self {
        Self::new(default_data_dir())
    }

    pub fn data_dir(&self) -> &Path {
        &self.data_dir
    }

    fn record_file(&self, date: &str) -> PathBuf {
        self.data_dir.join(format!("{}.json", date))
    }

    fn hourly_dir(&self) -> PathBuf {
        self.data_dir.join("hourly")
    }

    fn hourly_file(&self, date: &str) -> PathBuf {
        self.hourly_dir().join(format!("{}.json", date))
    }

    fn trend_file(&self) -> PathBuf {
        self.data_dir.join("trend.json")
    }

    /// 扫描数据目录中所有有效的日期 JSON 文件，返回 (日期字符串, 文件路径) 列表。
    /// 结果按日期字符串升序排列。
    fn list_date_jsons(&self) -> Vec<(String, PathBuf)> {
        let mut files = Vec::new();
        if let Ok(entries) = fs::read_dir(&self.data_dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                if path.extension().is_none_or(|e| e != "json") {
                    continue;
                }
                let name = path
                    .file_stem()
                    .unwrap_or_default()
                    .to_string_lossy()
                    .to_string();
                if is_date_json(&format!("{}.json", name)) {
                    files.push((name, path));
                }
            }
        }
        files.sort_by(|a, b| a.0.cmp(&b.0));
        files
    }

    /// 保存单个小时分布数据到 `hourly/YYYY-MM-DD.json`。
    pub fn save_daily_hourly(&self, hourly: &DailyHourly) -> io::Result<()> {
        let dir = self.hourly_dir();
        if !dir.exists() {
            fs::create_dir_all(&dir)?;
        }
        let path = self.hourly_file(&hourly.date);
        atomic_write(&path, &serialize_json(hourly)?)
    }

    /// 加载指定日期的小时分布数据，解析失败时打印警告并返回全新空分布。
    pub fn load_daily_hourly(&self, date: &str) -> DailyHourly {
        let path = self.hourly_file(date);
        if let Ok(content) = fs::read_to_string(&path) {
            if let Ok(mut h) = serde_json::from_str::<DailyHourly>(&content) {
                if h.hours.len() != 24 {
                    h.hours.resize(24, crate::models::HourlyRecord::default());
                }
                return h;
            }
        }
        DailyHourly::new(date.into())
    }

    /// 删除 `hourly/` 下超过保留天数的旧文件。
    pub fn cleanup_hourly_files(&self, keep_days: i64) {
        let dir = self.hourly_dir();
        if !dir.exists() {
            return;
        }
        let today = chrono::Local::now().date_naive();
        if let Ok(entries) = fs::read_dir(&dir) {
            for entry in entries.flatten() {
                let path = entry.path();
                let name = path.file_stem().unwrap_or_default().to_string_lossy();
                if let Ok(date) = chrono::NaiveDate::parse_from_str(&name, "%Y-%m-%d") {
                    if (today - date).num_days() > keep_days {
                        let _ = fs::remove_file(&path);
                    }
                }
            }
        }
    }

    /// 加载趋势缓存 `trend.json`，解析失败时打印警告并返回 `None`。
    pub fn load_multi_trend_cache(&self) -> Option<MultiTrendCache> {
        let path = self.trend_file();
        let content = fs::read_to_string(&path).ok()?;
        serde_json::from_str(&content).ok()
    }

    /// 保存多窗口趋势缓存到 `trend.json`。
    pub fn save_multi_trend_cache(&self, cache: &MultiTrendCache) -> io::Result<()> {
        let dir = &self.data_dir;
        if !dir.exists() {
            fs::create_dir_all(dir)?;
        }
        atomic_write(&self.trend_file(), &serialize_json(cache)?)
    }

    /// 加载用户配置 `config.json`。解析失败时打印警告并返回默认配置，**不覆盖原文件**。
    pub fn load_config(&self) -> crate::models::Config {
        let path = self.data_dir.join("config.json");
        if let Ok(content) = fs::read_to_string(&path) {
            match serde_json::from_str::<crate::models::Config>(&content) {
                Ok(cfg) => return cfg,
                Err(e) => eprintln!("警告: {} 解析失败 ({}), 使用默认配置", path.display(), e),
            }
        }
        crate::models::Config::default()
    }

    /// 将配置保存到 `config.json`。
    pub fn save_config(&self, cfg: &crate::models::Config) -> io::Result<()> {
        if !self.data_dir.exists() {
            fs::create_dir_all(&self.data_dir)?;
        }
        atomic_write(&self.data_dir.join("config.json"), &serialize_json(cfg)?)
    }

    /// 扫描历史日期 JSON 文件，按日期排序后取最近 `window_days` 天重建趋势缓存。
    pub fn rebuild_trend_cache(&self, window_days: u32) -> TrendCache {
        let mut records: Vec<(String, Record)> = Vec::new();
        for (name, path) in self.list_date_jsons() {
            if let Ok(content) = fs::read_to_string(&path) {
                if let Ok(record) = serde_json::from_str::<Record>(&content) {
                    records.push((name, record));
                }
            }
        }
        let keep = records.len().saturating_sub(window_days as usize);
        let days = records.into_iter().skip(keep).collect();
        TrendCache {
            updated_at: Local::now().to_rfc3339(),
            window_days,
            days,
        }
    }

    /// 加载指定月份的所有日期记录，按日期排序。
    pub fn load_month_records(&self, year: i32, month: u32) -> Vec<(String, u64)> {
        let mut records = Vec::new();
        for (name, path) in self.list_date_jsons() {
            if let Ok(date) = chrono::NaiveDate::parse_from_str(&name, "%Y-%m-%d") {
                if date.year() == year && date.month() == month {
                    if let Ok(content) = fs::read_to_string(&path) {
                        if let Ok(record) = serde_json::from_str::<Record>(&content) {
                            records.push((name, record.total_keys));
                        }
                    }
                }
            }
        }
        records
    }

    /// 检查指定月份是否存在任意日期的数据。
    pub fn has_data_for_month(&self, year: i32, month: u32) -> bool {
        for (name, _path) in self.list_date_jsons() {
            if let Ok(date) = chrono::NaiveDate::parse_from_str(&name, "%Y-%m-%d") {
                if date.year() == year && date.month() == month {
                    return true;
                }
            }
        }
        false
    }

    /// 加载指定日期的每日记录，解析失败时打印警告并返回 `None`。
    pub fn load_record_for_date(&self, date: &str) -> Option<Record> {
        let path = self.record_file(date);
        if !path.exists() {
            return None;
        }
        let content = fs::read_to_string(&path).ok()?;
        serde_json::from_str(&content).ok()
    }

    /// 保存每日记录到 `YYYY-MM-DD.json`。
    pub fn save_record_for_date(&self, record: &Record, date: &str) -> io::Result<()> {
        let dir = &self.data_dir;
        if !dir.exists() {
            fs::create_dir_all(dir)?;
        }
        atomic_write(&self.record_file(date), &serialize_json(record)?)
    }

    /// 将当前运行时状态写入 `today.tmp`（JSON 格式，兼容旧 CSV）。
    pub fn quick_save(&self, record: &Record, current_date: &str) {
        let dir = &self.data_dir;
        let _ = fs::create_dir_all(dir);
        let path = dir.join("today.tmp");
        let data = QuickSaveV2 {
            date: current_date.to_string(),
            record: record.clone(),
        };
        if let Ok(json) = serialize_json(&data) {
            let _ = atomic_write(&path, &json);
        }
    }

    /// 尝试从 `today.tmp` 恢复崩溃前的状态。先尝试 JSON V2，再回退旧 CSV。
    /// 若文件超过 24 小时或日期不匹配当前日期，则视为过期，不再恢复。
    pub fn try_load_quick_save(&self, current_date: &str) -> Option<Record> {
        let path = self.data_dir.join("today.tmp");
        let meta = fs::metadata(&path).ok()?;
        let mtime = meta.modified().ok()?;
        if mtime.elapsed().ok()? > std::time::Duration::from_secs(86400) {
            let _ = fs::remove_file(&path);
            return None;
        }
        let content = fs::read_to_string(&path).ok()?;

        // 尝试 V2 JSON 格式
        if let Ok(v2) = serde_json::from_str::<QuickSaveV2>(&content) {
            if v2.date == current_date {
                return Some(v2.record);
            }
            return None;
        }

        // 回退到旧 CSV 格式
        let parts: Vec<&str> = content.trim().split(',').collect();
        let date = parts.get(2)?;
        if *date != current_date {
            return None;
        }
        let mut record = Record {
            total_keys: parts.first()?.parse().ok()?,
            total_mouse: parts.get(1)?.parse().ok()?,
            ..Default::default()
        };
        if parts.len() == 5 {
            record.backspace_count = parts[3].parse().ok()?;
            record.delete_count = parts[4].parse().ok()?;
        }
        Some(record)
    }

    fn compute_max_json_mtime(&self, today_date: &str) -> Option<SystemTime> {
        let mut max = None;
        for (name, path) in self.list_date_jsons() {
            if name == today_date {
                continue;
            }
            if let Ok(mtime) = path.metadata().and_then(|m| m.modified()) {
                max = Some(max.map_or(mtime, |m: SystemTime| m.max(mtime)));
            }
        }
        max
    }

    fn load_history_cache(&self) -> Option<HistoryCache> {
        let path = self.data_dir.join("history.json");
        let content = fs::read_to_string(&path).ok()?;
        serde_json::from_str(&content).ok()
    }

    /// 保存历史摘要缓存到 `history.json`，附带同步时间戳。
    pub fn save_history_cache(&self, summary: &Record) -> io::Result<()> {
        let cache = HistoryCache {
            last_sync_time: Local::now().to_rfc3339(),
            summary: summary.clone(),
        };
        atomic_write(
            &self.data_dir.join("history.json"),
            &serialize_json(&cache)?,
        )
    }

    /// 加载历史摘要。优先使用 `history.json` 缓存（通过 mtime 校验 freshness），
    /// 缓存失效或不存在时全量扫描历史日期 JSON 并重建缓存。
    pub fn load_history_summary(&self, today_date: &str) -> Record {
        if !self.data_dir.exists() {
            return Record::default();
        }

        let cached = self.load_history_cache();
        let max_mtime = self.compute_max_json_mtime(today_date);

        if let (Some(cache), Some(mtime)) = (cached, max_mtime) {
            if let Ok(sync_time) = cache
                .last_sync_time
                .parse::<chrono::DateTime<chrono::Local>>()
            {
                if mtime <= sync_time.into() {
                    return cache.summary;
                }
            }
        }

        let mut summary = Record::default();
        for (name, path) in self.list_date_jsons() {
            if name == today_date {
                continue;
            }
            if let Ok(content) = fs::read_to_string(&path) {
                if let Ok(record) = serde_json::from_str::<Record>(&content) {
                    merge_record_into(&mut summary, &record);
                }
            }
        }

        let _ = self.save_history_cache(&summary);
        summary
    }
}

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

    #[test]
    fn test_merge_record_into_accumulates_counts() {
        let mut target = Record {
            total_keys: 10,
            total_mouse: 5,
            peak_kps: 3.0,
            peak_cps: 1.0,
            key_counts: [("A".to_string(), 3), ("B".to_string(), 7)]
                .into_iter()
                .collect(),
            mouse_counts: [("左键".to_string(), 5)].into_iter().collect(),
            backspace_count: 1,
            delete_count: 0,
        };
        let source = Record {
            total_keys: 20,
            total_mouse: 8,
            peak_kps: 5.0,
            peak_cps: 2.0,
            key_counts: [("B".to_string(), 3), ("C".to_string(), 10)]
                .into_iter()
                .collect(),
            mouse_counts: [("右键".to_string(), 8)].into_iter().collect(),
            backspace_count: 2,
            delete_count: 1,
        };
        merge_record_into(&mut target, &source);
        assert_eq!(target.total_keys, 30);
        assert_eq!(target.total_mouse, 13);
        assert_eq!(target.peak_kps, 5.0);
        assert_eq!(target.peak_cps, 2.0);
        assert_eq!(target.key_counts.get("A"), Some(&3));
        assert_eq!(target.key_counts.get("B"), Some(&10));
        assert_eq!(target.key_counts.get("C"), Some(&10));
        assert_eq!(target.mouse_counts.get("左键"), Some(&5));
        assert_eq!(target.mouse_counts.get("右键"), Some(&8));
        assert_eq!(target.backspace_count, 3);
        assert_eq!(target.delete_count, 1);
    }

    #[test]
    fn test_is_date_json_valid() {
        assert!(is_date_json("2024-01-15.json"));
        assert!(is_date_json("2023-12-31.json"));
    }

    #[test]
    fn test_is_date_json_invalid() {
        assert!(!is_date_json("history.json"));
        assert!(!is_date_json("trend.json"));
        assert!(!is_date_json("2024-13-01.json")); // 无效月份
        assert!(!is_date_json("2024-02-30.json")); // 无效日期
        assert!(!is_date_json("2024-01-1.json")); // 长度不足
        assert!(!is_date_json("24-01-01.json")); // 年份不足
        assert!(!is_date_json("2024/01/01.json")); // 分隔符错误
    }

    #[test]
    fn test_has_data_for_month() {
        let tmp = std::env::temp_dir().join("tapstat_test_has_data");
        let _ = std::fs::remove_dir_all(&tmp);
        std::fs::create_dir_all(&tmp).unwrap();

        let store = Store::new(&tmp);
        assert!(!store.has_data_for_month(2026, 4));

        let record = Record::default();
        std::fs::write(
            tmp.join("2026-04-01.json"),
            serde_json::to_string(&record).unwrap(),
        )
        .unwrap();

        assert!(store.has_data_for_month(2026, 4));
        assert!(!store.has_data_for_month(2026, 3));

        let _ = std::fs::remove_dir_all(&tmp);
    }

    #[test]
    fn test_load_month_records() {
        let tmp = std::env::temp_dir().join("tapstat_test_load_month");
        let _ = std::fs::remove_dir_all(&tmp);
        std::fs::create_dir_all(&tmp).unwrap();

        let store = Store::new(&tmp);
        assert!(store.load_month_records(2026, 4).is_empty());

        let record = Record {
            total_keys: 100,
            ..Default::default()
        };
        std::fs::write(
            tmp.join("2026-04-01.json"),
            serde_json::to_string(&record).unwrap(),
        )
        .unwrap();

        // 跨月数据不应被加载
        std::fs::write(
            tmp.join("2026-03-31.json"),
            serde_json::to_string(&Record::default()).unwrap(),
        )
        .unwrap();

        let records = store.load_month_records(2026, 4);
        assert_eq!(records.len(), 1);
        assert_eq!(records[0].0, "2026-04-01");
        assert_eq!(records[0].1, 100);

        let _ = std::fs::remove_dir_all(&tmp);
    }

    #[test]
    fn test_try_load_quick_save_v2_full_recovery() {
        let tmp = std::env::temp_dir().join("tapstat_test_qs_v2");
        let _ = std::fs::remove_dir_all(&tmp);
        std::fs::create_dir_all(&tmp).unwrap();

        let store = Store::new(&tmp);
        let date = "2024-06-01";

        let mut record = Record {
            total_keys: 1234,
            total_mouse: 56,
            backspace_count: 7,
            delete_count: 3,
            ..Default::default()
        };
        record.key_counts.insert("A".to_string(), 100);
        record.key_counts.insert("ShiftLeft".to_string(), 50);
        record.peak_kps = 12.5;

        store.quick_save(&record, date);

        let recovered = store.try_load_quick_save(date).unwrap();
        assert_eq!(recovered.total_keys, 1234);
        assert_eq!(recovered.total_mouse, 56);
        assert_eq!(recovered.backspace_count, 7);
        assert_eq!(recovered.delete_count, 3);
        assert_eq!(recovered.key_counts.get("A"), Some(&100));
        assert_eq!(recovered.key_counts.get("ShiftLeft"), Some(&50));
        assert_eq!(recovered.peak_kps, 12.5);

        // 日期不匹配时应返回 None
        assert!(store.try_load_quick_save("2024-06-02").is_none());

        let _ = std::fs::remove_dir_all(&tmp);
    }

    #[test]
    fn test_try_load_quick_save_csv_fallback() {
        let tmp = std::env::temp_dir().join("tapstat_test_qs_csv");
        let _ = std::fs::remove_dir_all(&tmp);
        std::fs::create_dir_all(&tmp).unwrap();

        let store = Store::new(&tmp);
        let date = "2024-06-01";

        let csv = "1234,56,2024-06-01,7,3";
        std::fs::write(tmp.join("today.tmp"), csv).unwrap();

        let recovered = store.try_load_quick_save(date).unwrap();
        assert_eq!(recovered.total_keys, 1234);
        assert_eq!(recovered.total_mouse, 56);
        assert_eq!(recovered.backspace_count, 7);
        assert_eq!(recovered.delete_count, 3);
        assert!(recovered.key_counts.is_empty());

        // 日期不匹配
        assert!(store.try_load_quick_save("2024-06-02").is_none());

        let _ = std::fs::remove_dir_all(&tmp);
    }

    #[test]
    fn test_cleanup_hourly_files_removes_old_files() {
        let tmp = std::env::temp_dir().join("tapstat_test_cleanup_hourly");
        let _ = std::fs::remove_dir_all(&tmp);
        std::fs::create_dir_all(tmp.join("hourly")).unwrap();

        let store = Store::new(&tmp);

        let today = chrono::Local::now().format("%Y-%m-%d").to_string();
        let old_date = (chrono::Local::now() - chrono::Duration::days(40))
            .format("%Y-%m-%d")
            .to_string();

        std::fs::write(tmp.join("hourly").join(format!("{}.json", today)), "{}").unwrap();
        std::fs::write(tmp.join("hourly").join(format!("{}.json", old_date)), "{}").unwrap();

        store.cleanup_hourly_files(30);

        assert!(tmp.join("hourly").join(format!("{}.json", today)).exists());
        assert!(!tmp
            .join("hourly")
            .join(format!("{}.json", old_date))
            .exists());

        let _ = std::fs::remove_dir_all(&tmp);
    }

    #[test]
    fn test_rebuild_trend_cache_sorted_and_windowed() {
        let tmp = std::env::temp_dir().join("tapstat_test_trend");
        let _ = std::fs::remove_dir_all(&tmp);
        std::fs::create_dir_all(&tmp).unwrap();

        let store = Store::new(&tmp);

        let mut record = Record::default();
        for i in 1..=5 {
            record.total_keys = i as u64 * 100;
            let date = format!("2024-01-{:02}", i + 10);
            std::fs::write(
                tmp.join(format!("{}.json", date)),
                serde_json::to_string(&record).unwrap(),
            )
            .unwrap();
        }

        let cache = store.rebuild_trend_cache(3);
        assert_eq!(cache.days.len(), 3);
        assert_eq!(cache.days[0].0, "2024-01-13");
        assert_eq!(cache.days[1].0, "2024-01-14");
        assert_eq!(cache.days[2].0, "2024-01-15");
        assert_eq!(cache.days[2].1.total_keys, 500);

        let _ = std::fs::remove_dir_all(&tmp);
    }

    #[test]
    fn test_load_history_summary_rebuilds_when_stale() {
        let tmp = std::env::temp_dir().join("tapstat_test_history");
        let _ = std::fs::remove_dir_all(&tmp);
        std::fs::create_dir_all(&tmp).unwrap();

        let store = Store::new(&tmp);

        let record1 = Record {
            total_keys: 100,
            ..Default::default()
        };
        std::fs::write(
            tmp.join("2024-01-10.json"),
            serde_json::to_string(&record1).unwrap(),
        )
        .unwrap();

        let summary1 = store.load_history_summary("2024-01-15");
        assert_eq!(summary1.total_keys, 100);

        // 新增历史文件，缓存应失效并重建
        let record2 = Record {
            total_keys: 200,
            ..Default::default()
        };
        std::fs::write(
            tmp.join("2024-01-11.json"),
            serde_json::to_string(&record2).unwrap(),
        )
        .unwrap();

        let summary2 = store.load_history_summary("2024-01-15");
        assert_eq!(summary2.total_keys, 300);

        let _ = std::fs::remove_dir_all(&tmp);
    }
}
