use std::collections::{HashMap, HashSet, VecDeque};
use std::time::{Duration, Instant};

use crate::input::{format_button, format_key, is_modifier};
use crate::models::{Config, DailyHourly, MultiTrendCache, Record, TrendCache, ViewMode};
use crate::storage::{merge_record_into, Store};
use chrono::{Datelike, Timelike};
use ratatui::style::Color;

/// 核心运行时统计数据。
pub struct Stats {
    pub total_keys: u64,
    pub total_mouse: u64,
    pub key_counts: HashMap<String, u64>,
    pub mouse_counts: HashMap<String, u64>,
    pub backspace_count: u64,
    pub delete_count: u64,
    pub key_window: VecDeque<Instant>,
    pub mouse_window: VecDeque<Instant>,
    pub session_peak_kps: f64,
    pub session_peak_cps: f64,
}

impl Stats {
    /// 从已持久化的基础记录恢复运行时状态。滑动窗口和会话峰值从零开始。
    fn new(base: &Record) -> Self {
        let mut key_counts = base.key_counts.clone();
        crate::storage::split_modifier_counts(&mut key_counts);

        Self {
            total_keys: base.total_keys,
            total_mouse: base.total_mouse,
            key_counts,
            mouse_counts: base.mouse_counts.clone(),
            backspace_count: base.backspace_count,
            delete_count: base.delete_count,
            key_window: VecDeque::new(),
            mouse_window: VecDeque::new(),
            session_peak_kps: 0.0,
            session_peak_cps: 0.0,
        }
    }

    /// 今日峰值 KPS = max(持久化峰值, 本次运行会话峰值)。
    pub(crate) fn today_peak_kps(&self, base: &Record) -> f64 {
        base.peak_kps.max(self.session_peak_kps)
    }

    /// 今日峰值 CPS = max(持久化峰值, 本次运行会话峰值)。
    pub(crate) fn today_peak_cps(&self, base: &Record) -> f64 {
        base.peak_cps.max(self.session_peak_cps)
    }

    /// 计算纠错率字符串：`(Backspace + Delete) / total_keys * 100%`。
    pub(crate) fn correction_rate_str(&self) -> String {
        if self.total_keys == 0 {
            return "0.00%".into();
        }
        let correction = self.backspace_count + self.delete_count;
        let rate = correction as f64 / self.total_keys as f64 * 100.0;
        format!("{:.2}%", rate)
    }

    /// 根据纠错率返回对应颜色：低(绿)、中(黄)、高(红)。
    pub(crate) fn correction_rate_color(&self) -> Color {
        if self.total_keys == 0 {
            return Color::Green;
        }
        let correction = self.backspace_count + self.delete_count;
        let rate = correction as f64 / self.total_keys as f64 * 100.0;
        match rate {
            r if r < 3.0 => Color::Green,
            r if r < 8.0 => Color::Yellow,
            _ => Color::Red,
        }
    }
}

/// 历史与持久化相关状态。
pub struct History {
    pub today_base: Record,
    pub history_summary: Record,
    pub current_date: String,
    pub hourly: DailyHourly,
    pub multi_trend: MultiTrendCache,
    pub trend_window: u32,
}

impl History {
    /// 加载今日基础数据、小时分布、趋势缓存和历史摘要。
    fn new(
        store: &Store,
        today_base: Record,
        current_date: String,
        default_trend_window: u32,
        trend_windows: &[u32],
    ) -> Self {
        let hourly = store.load_daily_hourly(&current_date);
        let mut multi_trend = store.load_multi_trend_cache().unwrap_or_default();
        for &w in trend_windows {
            multi_trend.ensure(store, w);
        }
        multi_trend.retain(|w| trend_windows.contains(w));
        let _ = store.save_multi_trend_cache(&multi_trend);
        Self {
            today_base,
            history_summary: store.load_history_summary(&current_date),
            current_date,
            hourly,
            multi_trend,
            trend_window: default_trend_window,
        }
    }

    /// 获取当前选中的趋势窗口缓存。窗口缺失时返回空缓存，避免 panic。
    pub(crate) fn current_trend(&self) -> &TrendCache {
        self.multi_trend.get(self.trend_window).unwrap_or_else(|| {
            // 窗口缺失时 fallback 到空缓存，避免 panic
            static EMPTY: std::sync::OnceLock<TrendCache> = std::sync::OnceLock::new();
            EMPTY.get_or_init(TrendCache::default)
        })
    }

    /// 历史累计按键总数 = 历史摘要 + 本次运行。
    pub(crate) fn all_time_keys(&self, stats: &Stats) -> u64 {
        self.history_summary.total_keys + stats.total_keys
    }

    /// 历史累计点击总数 = 历史摘要 + 本次运行。
    pub(crate) fn all_time_mouse(&self, stats: &Stats) -> u64 {
        self.history_summary.total_mouse + stats.total_mouse
    }

    /// 历史峰值 KPS = max(历史摘要峰值, 今日峰值)。
    fn all_time_peak_kps(&self, stats: &Stats) -> f64 {
        self.history_summary
            .peak_kps
            .max(stats.today_peak_kps(&self.today_base))
    }

    /// 历史峰值 CPS = max(历史摘要峰值, 今日峰值)。
    fn all_time_peak_cps(&self, stats: &Stats) -> f64 {
        self.history_summary
            .peak_cps
            .max(stats.today_peak_cps(&self.today_base))
    }

    /// 合并历史按键计数与本次运行计数，用于「历史」模式排行。
    pub(crate) fn merged_key_counts(&self, stats: &Stats) -> HashMap<String, u64> {
        let mut merged = self.history_summary.key_counts.clone();
        for (k, v) in &stats.key_counts {
            *merged.entry(k.clone()).or_insert(0) += *v;
        }
        merged
    }

    /// 合并历史鼠标计数与本次运行计数，用于「历史」模式排行。
    pub(crate) fn merged_mouse_counts(&self, stats: &Stats) -> HashMap<String, u64> {
        let mut merged = self.history_summary.mouse_counts.clone();
        for (k, v) in &stats.mouse_counts {
            *merged.entry(k.clone()).or_insert(0) += *v;
        }
        merged
    }
}

/// 输入追踪瞬态。
#[derive(Default)]
pub struct InputState {
    pub pressed_keys: HashSet<String>,
    pub combo_counts: HashMap<String, u64>,
}

/// UI 控制与定时器状态。
pub struct UiState {
    pub start_time: Instant,
    pub show_history: bool,
    pub view_mode: ViewMode,
    pub last_save: Instant,
    pub last_quick_save: Instant,
    pub last_message: Option<(String, Instant, Color)>,
    pub has_received_input: bool,
    pub running: bool,
    pub calendar_month_offset: i32,
    pub calendar_selected_day: Option<u32>,
}

impl Default for UiState {
    fn default() -> Self {
        Self {
            start_time: Instant::now(),
            show_history: false,
            view_mode: ViewMode::Dashboard,
            last_save: Instant::now(),
            last_quick_save: Instant::now(),
            last_message: None,
            has_received_input: false,
            running: true,
            calendar_month_offset: 0,
            calendar_selected_day: None,
        }
    }
}

impl UiState {
    /// 本次运行已持续的时长，格式 `HH:MM:SS`。
    pub(crate) fn elapsed_str(&self) -> String {
        let secs = self.start_time.elapsed().as_secs();
        format!(
            "{:02}:{:02}:{:02}",
            secs / 3600,
            (secs % 3600) / 60,
            secs % 60
        )
    }
}

/// 应用主结构。
pub struct App {
    pub store: Store,
    pub config: Config,
    pub stats: Stats,
    pub history: History,
    pub input: InputState,
    pub ui: UiState,
    /// Hourly 视图当前显示的目标日期（默认今天）。
    pub hourly_view_date: String,
}

impl App {
    /// 初始化应用状态：加载今日记录、崩溃恢复、构建历史与趋势缓存。
    pub fn new(store: Store, config: Config) -> Self {
        let current_date = chrono::Local::now().format("%Y-%m-%d").to_string();
        let mut today_base = store
            .load_record_for_date(&current_date)
            .unwrap_or_default();

        // 崩溃恢复：若 tmp 比 JSON 新，则恢复崩溃前的状态
        if let Some(quick_record) = store.try_load_quick_save(&current_date) {
            let json_mtime = store
                .data_dir()
                .join(format!("{}.json", current_date))
                .metadata()
                .ok()
                .and_then(|m| m.modified().ok());
            let tmp_mtime = store
                .data_dir()
                .join("today.tmp")
                .metadata()
                .ok()
                .and_then(|m| m.modified().ok());
            if let (Some(jm), Some(tm)) = (json_mtime, tmp_mtime) {
                if tm > jm {
                    if quick_record.key_counts.is_empty() {
                        // 旧 CSV 格式：只恢复标量，保留 JSON 中的分布数据
                        today_base.total_keys = quick_record.total_keys;
                        today_base.total_mouse = quick_record.total_mouse;
                        today_base.backspace_count = quick_record.backspace_count;
                        today_base.delete_count = quick_record.delete_count;
                    } else {
                        today_base = quick_record;
                    }
                }
            }
        }

        store.cleanup_hourly_files(config.hourly_keep_days);
        let history = History::new(
            &store,
            today_base,
            current_date.clone(),
            config.default_trend_window,
            &config.trend_windows,
        );
        let stats = Stats::new(&history.today_base);
        let mut app = Self {
            store,
            config,
            stats,
            history,
            input: InputState::default(),
            ui: UiState::default(),
            hourly_view_date: current_date.clone(),
        };
        app.update_rates();
        app
    }

    /// 尝试按 delta 切换日历月份，目标月份无数据时阻止切换并显示提示。
    pub(crate) fn try_shift_calendar_month(&mut self, delta: i32) {
        let today = match chrono::NaiveDate::parse_from_str(&self.history.current_date, "%Y-%m-%d")
        {
            Ok(d) => d,
            Err(_) => return,
        };
        let new_offset = self.ui.calendar_month_offset + delta;
        let total_months = today.year() * 12 + (today.month() as i32 - 1) + new_offset;
        let target_year = total_months / 12;
        let target_month = (total_months % 12).unsigned_abs() + 1;

        if self.store.has_data_for_month(target_year, target_month) {
            self.ui.calendar_month_offset = new_offset;
            // 切换月份后重置选中日期
            self.ui.calendar_selected_day = None;
        } else {
            self.ui.last_message = Some((
                format!("⚠️ {}年{}月：暂无数据", target_year, target_month),
                std::time::Instant::now(),
                Color::Yellow,
            ));
        }
    }

    /// 在日历中按 delta 天移动选中日期，支持跨月。目标月份无数据时阻止移动并提示。
    pub(crate) fn shift_calendar_selection(&mut self, delta: i32) {
        let today = match chrono::NaiveDate::parse_from_str(&self.history.current_date, "%Y-%m-%d")
        {
            Ok(d) => d,
            Err(_) => return,
        };

        let total_months =
            today.year() * 12 + (today.month() as i32 - 1) + self.ui.calendar_month_offset;
        let target_year = total_months / 12;
        let target_month = (total_months % 12).unsigned_abs() + 1;

        let current_day = self.ui.calendar_selected_day.unwrap_or_else(|| {
            if self.ui.calendar_month_offset == 0 {
                today.day()
            } else {
                1
            }
        });

        let current_date =
            match chrono::NaiveDate::from_ymd_opt(target_year, target_month, current_day) {
                Some(d) => d,
                None => return,
            };

        let new_date = current_date + chrono::Duration::days(delta as i64);
        let new_year = new_date.year();
        let new_month = new_date.month();
        let new_offset = (new_year - today.year()) * 12 + (new_month as i32 - today.month() as i32);

        // 跨出本月时才检查目标月份是否有数据；本月始终允许（含实时数据）
        if new_offset != 0 && !self.store.has_data_for_month(new_year, new_month) {
            self.ui.last_message = Some((
                format!("⚠️ {}年{}月：暂无数据", new_year, new_month),
                Instant::now(),
                Color::Yellow,
            ));
            return;
        }

        self.ui.calendar_month_offset = new_offset;
        self.ui.calendar_selected_day = Some(new_date.day());
    }

    /// 返回日历视图中当前选中日期的 `YYYY-MM-DD` 字符串。
    /// 如果未手动选择，默认返回今天（offset == 0 时）或该月 1 号。
    pub(crate) fn selected_calendar_date(&self) -> Option<String> {
        let today =
            chrono::NaiveDate::parse_from_str(&self.history.current_date, "%Y-%m-%d").ok()?;
        let total_months =
            today.year() * 12 + (today.month() as i32 - 1) + self.ui.calendar_month_offset;
        let target_year = total_months / 12;
        let target_month = (total_months % 12).unsigned_abs() + 1;

        let day = self.ui.calendar_selected_day.unwrap_or_else(|| {
            if self.ui.calendar_month_offset == 0 {
                today.day()
            } else {
                1
            }
        });

        chrono::NaiveDate::from_ymd_opt(target_year, target_month, day)
            .map(|d| d.format("%Y-%m-%d").to_string())
    }

    /// 在 Hourly 视图中按 delta 天翻页。目标日期无记录时阻止并提示。
    pub(crate) fn shift_hourly_date(&mut self, delta: i32) {
        let current = match chrono::NaiveDate::parse_from_str(&self.hourly_view_date, "%Y-%m-%d") {
            Ok(d) => d,
            Err(_) => return,
        };
        let new_date = current + chrono::Duration::days(delta as i64);
        let new_date_str = new_date.format("%Y-%m-%d").to_string();

        // 今天永远允许
        if new_date_str == self.history.current_date {
            self.hourly_view_date = new_date_str;
            return;
        }

        if self.store.load_record_for_date(&new_date_str).is_some() {
            self.hourly_view_date = new_date_str;
        } else {
            self.ui.last_message = Some((
                format!("⚠️ {}：暂无数据", new_date_str),
                Instant::now(),
                Color::Yellow,
            ));
        }
    }

    /// 获取 Hourly 视图当前应显示的 24 小时数据。
    /// 如果是今天，返回内存中的实时数据；否则从磁盘加载。
    pub(crate) fn hourly_data(&self) -> DailyHourly {
        if self.hourly_view_date == self.history.current_date {
            self.history.hourly.clone()
        } else {
            self.store.load_daily_hourly(&self.hourly_view_date)
        }
    }

    /// 根据当前运行时状态构造一个可用于持久化的 `Record` 快照。
    pub(crate) fn current_record(&self) -> Record {
        Record {
            total_keys: self.stats.total_keys,
            total_mouse: self.stats.total_mouse,
            peak_kps: self.stats.today_peak_kps(&self.history.today_base),
            peak_cps: self.stats.today_peak_cps(&self.history.today_base),
            key_counts: self.stats.key_counts.clone(),
            mouse_counts: self.stats.mouse_counts.clone(),
            backspace_count: self.stats.backspace_count,
            delete_count: self.stats.delete_count,
        }
    }

    /// 处理单个全局输入事件，按类型分发给子处理器。
    pub fn on_input(&mut self, event_type: rdev::EventType) {
        match event_type {
            rdev::EventType::KeyPress(key) => self.on_key_press(key),
            rdev::EventType::KeyRelease(key) => self.on_key_release(key),
            rdev::EventType::ButtonPress(btn) => self.on_mouse_press(btn),
            _ => {}
        }
        self.ui.has_received_input = true;
        self.maybe_quick_save();
    }

    fn on_key_press(&mut self, key: rdev::Key) {
        let now = Instant::now();
        let hour = chrono::Local::now().hour() as usize;
        let name = format_key(&key);
        let was_pressed = self.input.pressed_keys.contains(&name);

        if !was_pressed {
            let has_modifier_pressed = !self.input.pressed_keys.is_empty()
                && self.input.pressed_keys.iter().any(|k| is_modifier(k));
            if has_modifier_pressed
                && (!is_modifier(&name) || !self.input.pressed_keys.contains(&name))
            {
                let mut combo: Vec<String> = self.input.pressed_keys.iter().cloned().collect();
                combo.push(name.clone());
                combo.sort();
                // 组合键统计合并左右 modifier（如 ShiftLeft+A → Shift+A）
                let combo_label = combo
                    .iter()
                    .map(|k| match k.as_str() {
                        "ShiftLeft" | "ShiftRight" => "Shift",
                        "CtrlLeft" | "CtrlRight" => "Ctrl",
                        "AltLeft" | "AltRight" => "Alt",
                        "CmdLeft" | "CmdRight" => "Cmd",
                        _ => k,
                    })
                    .collect::<Vec<_>>()
                    .join("+");
                *self.input.combo_counts.entry(combo_label).or_insert(0) += 1;
            }
            self.input.pressed_keys.insert(name.clone());
        }

        // 公共逻辑：autorepeat 和首次按下都要更新计数
        self.stats.total_keys += 1;
        self.history.hourly.hours[hour].total_keys += 1;
        self.stats.key_window.push_back(now);
        *self.stats.key_counts.entry(name.clone()).or_insert(0) += 1;
        match name.as_str() {
            "Backspace" => self.stats.backspace_count += 1,
            "Delete" => self.stats.delete_count += 1,
            _ => {}
        }
    }

    fn on_key_release(&mut self, key: rdev::Key) {
        let name = format_key(&key);
        self.input.pressed_keys.remove(&name);
    }

    fn on_mouse_press(&mut self, btn: rdev::Button) {
        let now = Instant::now();
        let hour = chrono::Local::now().hour() as usize;
        self.stats.total_mouse += 1;
        self.history.hourly.hours[hour].total_mouse += 1;
        let name = format_button(&btn);
        *self.stats.mouse_counts.entry(name).or_insert(0) += 1;
        self.stats.mouse_window.push_back(now);
    }

    fn maybe_quick_save(&mut self) {
        if self.ui.last_quick_save.elapsed()
            >= Duration::from_secs(self.config.quick_save_interval_secs)
        {
            self.store
                .quick_save(&self.current_record(), &self.history.current_date);
            self.ui.last_quick_save = Instant::now();
        }
    }

    /// 清理过期的 1 秒滑动窗口数据，并按配置间隔触发自动保存。
    pub fn update_rates(&mut self) {
        let cutoff = Instant::now() - Duration::from_secs(1);
        while self.stats.key_window.front().is_some_and(|t| *t < cutoff) {
            self.stats.key_window.pop_front();
        }
        while self.stats.mouse_window.front().is_some_and(|t| *t < cutoff) {
            self.stats.mouse_window.pop_front();
        }
        let kps = self.stats.key_window.len() as f64;
        let cps = self.stats.mouse_window.len() as f64;
        if kps > self.stats.session_peak_kps {
            self.stats.session_peak_kps = kps;
        }
        if cps > self.stats.session_peak_cps {
            self.stats.session_peak_cps = cps;
        }

        if self.ui.last_save.elapsed() >= Duration::from_secs(self.config.auto_save_interval_secs) {
            self.check_date_rollover();
            let record = self.current_record();
            if let Err(e) = self
                .store
                .save_record_for_date(&record, &self.history.current_date)
            {
                self.ui.last_message =
                    Some((format!("自动保存失败: {}", e), Instant::now(), Color::Red));
            } else {
                match std::fs::remove_file(self.store.data_dir().join("today.tmp")) {
                    Ok(()) => {}
                    Err(e) if e.kind() == std::io::ErrorKind::NotFound => {}
                    Err(e) => {
                        self.ui.last_message = Some((
                            format!("删除 today.tmp 失败: {}", e),
                            Instant::now(),
                            Color::Red,
                        ));
                    }
                }
                if let Err(e) = self.store.save_daily_hourly(&self.history.hourly) {
                    self.ui.last_message = Some((
                        format!("小时数据保存失败: {}", e),
                        Instant::now(),
                        Color::Red,
                    ));
                }
            }
            self.ui.last_save = Instant::now();
        }

        if let Some((_, time, _)) = &self.ui.last_message {
            if time.elapsed() >= Duration::from_secs(self.config.message_display_secs) {
                self.ui.last_message = None;
            }
        }
    }

    /// 检测是否跨天。若跨天则归档旧日数据、合并历史、重置会话状态。
    fn check_date_rollover(&mut self) {
        let new_date = chrono::Local::now().format("%Y-%m-%d").to_string();
        if new_date == self.history.current_date {
            return;
        }
        let old_record = self.current_record();
        if let Err(e) = self
            .store
            .save_record_for_date(&old_record, &self.history.current_date)
        {
            self.ui.last_message =
                Some((format!("日期归档失败: {}", e), Instant::now(), Color::Red));
        }

        let old_hourly = std::mem::replace(
            &mut self.history.hourly,
            DailyHourly::new(self.history.current_date.clone()),
        );
        if let Err(e) = self.store.save_daily_hourly(&old_hourly) {
            self.ui.last_message = Some((
                format!("小时数据归档失败: {}", e),
                Instant::now(),
                Color::Red,
            ));
        }

        merge_record_into(&mut self.history.history_summary, &old_record);
        if let Err(e) = self.store.save_history_cache(&self.history.history_summary) {
            self.ui.last_message = Some((
                format!("历史缓存更新失败: {}", e),
                Instant::now(),
                Color::Red,
            ));
        }

        let fresh = self
            .store
            .load_record_for_date(&new_date)
            .unwrap_or_default();
        self.history.current_date = new_date.clone();
        self.hourly_view_date = new_date;
        self.history.today_base = fresh.clone();
        self.stats = Stats::new(&fresh);
        self.ui.last_save = Instant::now();
        self.input = InputState::default();
        self.history.hourly = self.store.load_daily_hourly(&self.history.current_date);
        for &w in &self.config.trend_windows {
            self.history.multi_trend.ensure(&self.store, w);
        }
        self.history
            .multi_trend
            .retain(|w| self.config.trend_windows.contains(w));
        if let Err(e) = self.store.save_multi_trend_cache(&self.history.multi_trend) {
            self.ui.last_message = Some((
                format!("趋势缓存保存失败: {}", e),
                Instant::now(),
                Color::Red,
            ));
        }
        self.ui.last_message = Some((
            "已切换至新一天的统计".into(),
            Instant::now(),
            Color::DarkGray,
        ));
    }

    /// 退出时在标准输出打印最终统计报告。
    pub fn final_report(&self) {
        let all_keys = self.history.all_time_keys(&self.stats);
        let all_mouse = self.history.all_time_mouse(&self.stats);
        println!("TapStat - 最终统计");
        println!("===================");
        println!("本次运行: {}", self.ui.elapsed_str());
        println!("今日按键: {}", self.stats.total_keys);
        println!("今日点击: {}", self.stats.total_mouse);
        println!("纠错率: {}", self.stats.correction_rate_str());
        println!("历史按键: {}", all_keys);
        println!("历史点击: {}", all_mouse);
        println!(
            "今日峰值 KPS: {:.1}",
            self.stats.today_peak_kps(&self.history.today_base)
        );
        println!(
            "今日峰值 CPS: {:.1}",
            self.stats.today_peak_cps(&self.history.today_base)
        );
        println!(
            "历史峰值 KPS: {:.1}",
            self.history.all_time_peak_kps(&self.stats)
        );
        println!(
            "历史峰值 CPS: {:.1}",
            self.history.all_time_peak_cps(&self.stats)
        );
        println!();
        println!("键盘 Top 15（今日）");
        println!("--------------------");
        for (name, count) in sorted_top(&self.stats.key_counts, 15) {
            println!("  {:12} {}", name, count);
        }
        println!();
        println!("组合键 Top 10");
        println!("--------------------");
        for (name, count) in sorted_top(&self.input.combo_counts, 10) {
            println!("  {:16} {}", name, count);
        }
        println!();
        println!("鼠标排行（今日）");
        println!("--------------------");
        for (name, count) in sorted_top(&self.stats.mouse_counts, 10) {
            println!("  {:12} {}", name, count);
        }
        println!();
        println!(
            "数据已保存到 {}",
            self.store
                .data_dir()
                .join(format!("{}.json", self.history.current_date))
                .display()
        );
        println!("已退出。");
    }
}

/// 对 HashMap 按值降序、键升序排序，并取前 n 名。
pub fn sorted_top(map: &HashMap<String, u64>, n: usize) -> Vec<(&String, &u64)> {
    let mut vec: Vec<_> = map.iter().collect();
    vec.sort_by(|a, b| b.1.cmp(a.1).then_with(|| a.0.cmp(b.0)));
    vec.into_iter().take(n).collect()
}

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

    #[test]
    fn test_sorted_top_basic() {
        let mut map = HashMap::new();
        map.insert("A".to_string(), 10);
        map.insert("B".to_string(), 30);
        map.insert("C".to_string(), 20);
        let top = sorted_top(&map, 2);
        assert_eq!(top.len(), 2);
        assert_eq!(top[0].0, "B");
        assert_eq!(top[1].0, "C");
    }

    #[test]
    fn test_sorted_top_tie_breaker() {
        let mut map = HashMap::new();
        map.insert("Z".to_string(), 10);
        map.insert("A".to_string(), 10);
        map.insert("M".to_string(), 10);
        let top = sorted_top(&map, 3);
        assert_eq!(top[0].0, "A"); // 同值按键名字母序升序
        assert_eq!(top[1].0, "M");
        assert_eq!(top[2].0, "Z");
    }

    #[test]
    fn test_sorted_top_n_larger_than_map() {
        let mut map = HashMap::new();
        map.insert("X".to_string(), 5);
        let top = sorted_top(&map, 10);
        assert_eq!(top.len(), 1);
    }

    #[test]
    fn test_correction_rate_empty() {
        let stats = Stats::new(&Record::default());
        assert_eq!(stats.correction_rate_str(), "0.00%");
        assert_eq!(stats.correction_rate_color(), Color::Green);
    }

    #[test]
    fn test_correction_rate_levels() {
        let base = Record::default();
        let mut stats = Stats::new(&base);
        stats.total_keys = 100;
        stats.backspace_count = 2;
        stats.delete_count = 0;
        assert_eq!(stats.correction_rate_str(), "2.00%");
        assert_eq!(stats.correction_rate_color(), Color::Green);

        stats.backspace_count = 5;
        assert_eq!(stats.correction_rate_str(), "5.00%");
        assert_eq!(stats.correction_rate_color(), Color::Yellow);

        stats.backspace_count = 10;
        assert_eq!(stats.correction_rate_str(), "10.00%");
        assert_eq!(stats.correction_rate_color(), Color::Red);
    }

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

        let store = Store::new(&tmp);
        let yesterday = "2024-01-15".to_string();

        let yesterday_base = Record {
            total_keys: 50,
            ..Default::default()
        };

        let history = History::new(&store, yesterday_base.clone(), yesterday.clone(), 7, &[7]);

        let mut app = App {
            store,
            config: Config::default(),
            stats: Stats::new(&yesterday_base),
            history,
            input: InputState::default(),
            ui: UiState::default(),
            hourly_view_date: yesterday.clone(),
        };

        app.stats.total_keys = 150;
        app.stats.backspace_count = 5;
        app.stats.delete_count = 2;

        app.check_date_rollover();

        // 验证昨天已归档
        let archived = app.store.load_record_for_date(&yesterday).unwrap();
        assert_eq!(archived.total_keys, 150);
        assert_eq!(archived.backspace_count, 5);
        assert_eq!(archived.delete_count, 2);

        // 验证状态已重置
        assert_eq!(app.stats.total_keys, 0);
        assert_eq!(app.stats.backspace_count, 0);
        assert_ne!(app.history.current_date, yesterday);
        assert_eq!(app.history.history_summary.total_keys, 150);

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

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

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

        let history = History::new(&store, Record::default(), date.clone(), 7, &[7]);

        let mut app = App {
            store,
            config: Config::default(),
            stats: Stats::new(&Record::default()),
            history,
            input: InputState::default(),
            ui: UiState::default(),
            hourly_view_date: date,
        };

        app.ui.calendar_month_offset = 0;
        app.ui.calendar_selected_day = Some(31);

        // 2月无数据，+1天应被阻止
        app.shift_calendar_selection(1);
        assert_eq!(app.ui.calendar_month_offset, 0);
        assert_eq!(app.ui.calendar_selected_day, Some(31));
        assert!(app.ui.last_message.is_some());

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

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

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

        // 在2月1日创建数据，使跨月导航允许
        let record = Record {
            total_keys: 1,
            ..Default::default()
        };
        std::fs::write(
            tmp.join("2024-02-01.json"),
            serde_json::to_string(&record).unwrap(),
        )
        .unwrap();

        let history = History::new(&store, Record::default(), date.clone(), 7, &[7]);

        let mut app = App {
            store,
            config: Config::default(),
            stats: Stats::new(&Record::default()),
            history,
            input: InputState::default(),
            ui: UiState::default(),
            hourly_view_date: date,
        };

        app.ui.calendar_month_offset = 0;
        app.ui.calendar_selected_day = Some(31);

        // +1天 → 2月1日（有数据，允许）
        app.shift_calendar_selection(1);
        assert_eq!(app.ui.calendar_month_offset, 1);
        assert_eq!(app.ui.calendar_selected_day, Some(1));

        // -1天 → 回到1月31日
        app.shift_calendar_selection(-1);
        assert_eq!(app.ui.calendar_month_offset, 0);
        assert_eq!(app.ui.calendar_selected_day, Some(31));

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