use anyhow::{bail, Context, Result};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fs;
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct AppConfig {
    pub display: DisplayConfig,
    pub playlist: Vec<VideoItem>,
    pub transition: TransitionConfig,
    pub playback: PlaybackConfig,
    pub scenes: ScenesConfig,
    pub remote_control: RemoteControlConfig,
    #[serde(default)]
    pub ble: BleConfig,
    /// 角色元信息 (M2.1 新增，向后兼容旧配置)
    #[serde(default)]
    pub character: CharacterConfig,
    /// AI 对话配置 (M2.1 新增)
    #[serde(default)]
    pub ai: AiConfig,
    #[serde(default)]
    pub source_path: PathBuf,
    #[serde(default)]
    pub source_dir: PathBuf,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct DisplayConfig {
    pub fullscreen: bool,
    pub window_title: String,
    pub rotation: i32,
    pub flip_horizontal: bool,
    pub flip_vertical: bool,
    #[serde(default)]
    pub offset_x: i32,
    #[serde(default)]
    pub offset_y: i32,
    #[serde(default)]
    pub prevent_screen_lock: bool,
    #[serde(default = "default_render_width")]
    pub render_width: i32,
    #[serde(default = "default_render_height")]
    pub render_height: i32,
    #[serde(default)]
    pub output_width: Option<i32>,
    #[serde(default)]
    pub output_height: Option<i32>,
    #[serde(default)]
    pub scale_mode: ScaleMode,
    #[serde(default = "default_allow_upscale")]
    pub allow_upscale: bool,
    pub perspective_correction: PerspectiveCorrectionConfig,
    #[serde(default)]
    pub chroma_key: ChromaKeyConfig,
    #[serde(default)]
    pub brightness_adjust: BrightnessAdjustConfig,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "lowercase")]
pub enum ScaleMode {
    #[default]
    Fit,
    Stretch,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PerspectiveCorrectionConfig {
    pub enabled: bool,
    pub points: Vec<[i32; 2]>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ChromaKeyConfig {
    #[serde(default)]
    pub enabled: bool,
    #[serde(default = "default_hsv_min")]
    pub hsv_min: [i32; 3],
    #[serde(default = "default_hsv_max")]
    pub hsv_max: [i32; 3],
    #[serde(default)]
    pub invert: bool,
    #[serde(default)]
    pub feather: i32,
}

impl Default for ChromaKeyConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            hsv_min: default_hsv_min(),
            hsv_max: default_hsv_max(),
            invert: false,
            feather: 0,
        }
    }
}

fn default_hsv_min() -> [i32; 3] {
    [0, 0, 200]
}

fn default_hsv_max() -> [i32; 3] {
    [180, 30, 255]
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BrightnessAdjustConfig {
    #[serde(default)]
    pub enabled: bool,
    #[serde(default = "default_subject_boost")]
    pub subject_boost: f64,
    #[serde(default = "default_background_suppress")]
    pub background_suppress: f64,
    #[serde(default = "default_brightness_threshold")]
    pub threshold: i32,
}

impl Default for BrightnessAdjustConfig {
    fn default() -> Self {
        Self {
            enabled: false,
            subject_boost: default_subject_boost(),
            background_suppress: default_background_suppress(),
            threshold: default_brightness_threshold(),
        }
    }
}

fn default_subject_boost() -> f64 {
    1.5
}
fn default_background_suppress() -> f64 {
    0.3
}
fn default_brightness_threshold() -> i32 {
    30
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct VideoItem {
    pub id: String,
    pub path: String,
    pub duration: Option<f64>,
    #[serde(default = "default_loop_count")]
    pub loop_count: i32,
    #[serde(default)]
    pub random_loop_range: Option<[i32; 2]>,
}

fn default_loop_count() -> i32 {
    1
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TransitionType {
    Fade,
    Cut,
    None,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct TransitionConfig {
    pub enabled: bool,
    #[serde(rename = "type")]
    pub transition_type: TransitionType,
    pub duration: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PlaybackConfig {
    pub loop_playlist: bool,
    pub auto_start: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScenesConfig {
    #[serde(default)]
    pub rest: Vec<VideoItem>,
    #[serde(default)]
    pub active: Vec<VideoItem>,
    #[serde(default)]
    pub sleep: Vec<VideoItem>,
    #[serde(default)]
    pub interact: Vec<VideoItem>,
    #[serde(default)]
    pub state_machine: Option<StateMachineConfig>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateMachineConfig {
    pub initial_state: String,
    pub states: HashMap<String, StateConfig>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateConfig {
    pub name: String,
    #[serde(default)]
    pub mode: StateMode,
    pub sequence: Vec<AnimationStep>,
    #[serde(default)]
    pub next_state: Option<String>,
    #[serde(default)]
    pub next_states: Option<Vec<NextStateEntry>>,
    #[serde(default)]
    pub transitions: Vec<StateTransition>,
    #[serde(default = "default_weight")]
    pub weight: f32,
    #[serde(default)]
    pub defer_triggers: bool,
    #[serde(default)]
    pub ignore_triggers: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NextStateEntry {
    pub state: String,
    #[serde(default = "default_weight")]
    pub weight: f32,
}

fn default_weight() -> f32 {
    1.0
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
pub enum StateMode {
    #[default]
    FreeMode,
    InteractiveMode,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AnimationStep {
    pub video_id: String,
    #[serde(default)]
    pub loop_count: Option<i32>,
    #[serde(default)]
    pub random_loop_range: Option<[i32; 2]>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StateTransition {
    pub trigger: TriggerType,
    pub target_state: String,
    #[serde(default)]
    pub priority: i32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TriggerType {
    Button { name: String },
    Voice { keyword: String },
    Sensor { name: String },
    Timer { seconds: f64 },
    Random { probability: f64 },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RemoteControlConfig {
    pub enabled: bool,
    pub host: String,
    pub port: u16,
}

/// BLE 配网配置
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct BleConfig {
    #[serde(default = "default_ble_enabled")]
    pub enabled: bool,
    #[serde(default = "default_ble_device_name")]
    pub device_name: String,
}

impl Default for BleConfig {
    fn default() -> Self {
        Self {
            enabled: default_ble_enabled(),
            device_name: default_ble_device_name(),
        }
    }
}

fn default_ble_enabled() -> bool {
    true
}
fn default_ble_device_name() -> String {
    "Showen".to_string()
}

// ── 角色元信息 (M2.1) ──

/// 角色类型
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum CharacterType {
    #[default]
    Pet,
    Human,
    Singer,
    /// Live2D 数字人 (渲染策略不同于视频)
    Live2d,
}

/// 渲染类型 (决定显示策略)
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum RenderType {
    /// 视频播放 (OpenCV 播放预渲染 mp4)
    #[default]
    Video,
    /// Live2D 渲染 (Web 前端渲染 Live2D 模型)
    Live2d,
    /// 纯文字 (无画面)
    Text,
}

/// 角色元信息配置块（内容包的一部分，切角色即切人设）
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct CharacterConfig {
    /// 角色显示名 (如 "小汪")
    #[serde(default)]
    pub name: String,
    /// 角色类型
    #[serde(default)]
    pub character_type: CharacterType,
    /// 渲染类型 (决定显示策略)
    #[serde(default)]
    pub render_type: RenderType,
    /// 封面图相对路径 (内容包内)
    #[serde(default)]
    pub cover_image: Option<String>,
    /// 人设 prompt (LLM system prompt)
    #[serde(default)]
    pub persona_prompt: String,
    /// 最大回复 token 数
    #[serde(default = "default_character_max_tokens")]
    pub max_tokens: u16,
    /// TTS 音色标识 (piper 模型 ID，留空用默认)
    #[serde(default)]
    pub tts_voice: Option<String>,
    /// talk 状态名 (语音回合期间切换到，留空用 "talk")
    #[serde(default = "default_talk_state")]
    pub talk_state: String,
    /// Live2D 模型路径 (render_type=live2d 时使用)
    #[serde(default)]
    pub live2d_model: Option<String>,
    /// Live2D 模型配置 JSON 路径
    #[serde(default)]
    pub live2d_config: Option<String>,
}

fn default_character_max_tokens() -> u16 {
    128
}
fn default_talk_state() -> String {
    "talk".to_string()
}

// ── AI 配置 (M2.1) ──

/// LLM 后端类型
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum LlmBackend {
    #[default]
    Local,
    /// 云端 (V1 仅占位，配置时返回"暂未支持")
    Cloud,
}

/// AI 推理配置
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(deny_unknown_fields)]
pub struct AiConfig {
    /// LLM 后端 (local/cloud)
    #[serde(default)]
    pub backend: LlmBackend,
    /// 云端 endpoint (预留，V1 不实现)
    #[serde(default)]
    pub cloud_endpoint: Option<String>,
    /// 云端 API key (预留)
    #[serde(default)]
    pub cloud_api_key: Option<String>,
    /// 模型存储目录 (默认 model_store/)
    #[serde(default = "default_model_store")]
    pub model_store: PathBuf,
    /// 工具目录 (whisper-cli/llama-cli/piper 二进制所在)
    #[serde(default = "default_tools_dir")]
    pub tools_dir: PathBuf,
    /// whisper-cli 依赖库路径 (libggml*.so 所在，部署时标定)
    #[serde(default)]
    pub whisper_lib_dir: Option<PathBuf>,
    /// piper 依赖库路径 (libespeak-ng.so / libpiper_phonemize.so 所在)
    #[serde(default)]
    pub piper_lib_dir: Option<PathBuf>,
    /// piper 模型 config json 路径 (.onnx.json)
    #[serde(default)]
    pub piper_config: Option<PathBuf>,
    /// piper espeak-ng-data 目录路径
    #[serde(default)]
    pub espeak_data_dir: Option<PathBuf>,
    /// 临时文件目录
    #[serde(default = "default_tmp_dir")]
    pub tmp_dir: PathBuf,
    /// 上下文窗口轮数
    #[serde(default = "default_context_window")]
    pub context_window: usize,
}

fn default_model_store() -> PathBuf {
    PathBuf::from("model_store")
}
fn default_tools_dir() -> PathBuf {
    PathBuf::from("tools")
}
fn default_tmp_dir() -> PathBuf {
    PathBuf::from("/tmp/showen_ai")
}
fn default_context_window() -> usize {
    5
}

// ── 加载与验证 ──

impl AppConfig {
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path = path.as_ref();
        let raw = fs::read_to_string(path)
            .with_context(|| format!("读取配置文件失败: {}", path.display()))?;
        let mut config: AppConfig = serde_json::from_str(&raw)
            .with_context(|| format!("解析配置 JSON 失败: {}", path.display()))?;
        config.set_source_path(path)?;
        config.validate()?;
        Ok(config)
    }

    fn set_source_path(&mut self, source_path: &Path) -> Result<()> {
        if source_path.as_os_str().is_empty() {
            bail!("配置文件路径不能为空");
        }
        self.source_path = source_path
            .canonicalize()
            .unwrap_or_else(|_| absolute_from_current_dir(source_path));
        self.source_dir = self
            .source_path
            .parent()
            .map(Path::to_path_buf)
            .unwrap_or_else(|| PathBuf::from("."));
        Ok(())
    }

    pub fn validate(&self) -> Result<()> {
        self.display.validate()?;

        if self.playlist.is_empty() {
            bail!("playlist 不能为空");
        }

        let mut playlist_ids = HashSet::new();
        for (index, item) in self.playlist.iter().enumerate() {
            item.validate(&format!("playlist[{index}]"))?;
            if !playlist_ids.insert(item.id.as_str()) {
                bail!("playlist[{index}] id '{}' 重复", item.id);
            }
        }

        self.transition.validate()?;
        self.scenes.validate(&playlist_ids)?;
        self.remote_control.validate()?;

        Ok(())
    }

    pub fn validate_paths(&self) -> Result<()> {
        for item in &self.playlist {
            let full_path = self.resolve_media_path(item);
            if !full_path.exists() {
                bail!(
                    "视频文件 '{}' 不存在: {} (相对于 {})",
                    item.id,
                    full_path.display(),
                    self.source_path.display()
                );
            }
        }
        Ok(())
    }

    pub fn resolve_media_path(&self, item: &VideoItem) -> PathBuf {
        let media_path = Path::new(&item.path);
        if media_path.is_absolute() {
            media_path.to_path_buf()
        } else {
            self.source_dir.join(media_path)
        }
    }
}

pub fn parse_str(raw: &str, source_path: impl Into<PathBuf>) -> Result<AppConfig> {
    let source_path = source_path.into();
    let mut config: AppConfig = serde_json::from_str(raw).context("解析配置 JSON 失败")?;
    config.set_source_path(&source_path)?;
    config.validate()?;
    Ok(config)
}

fn absolute_from_current_dir(path: &Path) -> PathBuf {
    if path.is_absolute() {
        return path.to_path_buf();
    }
    std::env::current_dir()
        .map(|cd| cd.join(path))
        .unwrap_or_else(|_| path.to_path_buf())
}

fn default_render_width() -> i32 {
    1024
}
fn default_render_height() -> i32 {
    1024
}
fn default_allow_upscale() -> bool {
    true
}

impl DisplayConfig {
    pub fn validate(&self) -> Result<()> {
        if self.window_title.trim().is_empty() {
            bail!("display.window_title 不能为空");
        }

        if self.render_width <= 0 {
            bail!("display.render_width 必须大于 0");
        }

        if self.render_height <= 0 {
            bail!("display.render_height 必须大于 0");
        }

        if !matches!(self.rotation, 0 | 90 | 180 | 270) {
            bail!("display.rotation 只能是 0/90/180/270");
        }

        self.perspective_correction.validate()?;
        self.chroma_key.validate()?;
        self.brightness_adjust.validate()?;

        Ok(())
    }
}

impl PerspectiveCorrectionConfig {
    pub fn validate(&self) -> Result<()> {
        let point_count = self.points.len();
        if point_count != 0 && point_count != 4 {
            bail!("display.perspective_correction.points 必须为 0 或 4 个点");
        }

        if self.enabled && point_count != 4 {
            bail!("display.perspective_correction 启用时必须提供 4 个点");
        }

        Ok(())
    }
}

impl ChromaKeyConfig {
    pub fn validate(&self) -> Result<()> {
        let hsv_ranges = [180, 255, 255]; // H, S, V max values
        for (index, &max_val) in hsv_ranges.iter().enumerate() {
            validate_hsv_component(
                &format!("display.chroma_key.hsv_min[{index}]"),
                self.hsv_min[index], 0, max_val,
            )?;
            validate_hsv_component(
                &format!("display.chroma_key.hsv_max[{index}]"),
                self.hsv_max[index], 0, max_val,
            )?;
            if self.hsv_min[index] > self.hsv_max[index] {
                bail!("display.chroma_key hsv_min[{index}] 不能大于 hsv_max[{index}]");
            }
        }

        if self.feather < 0 {
            bail!("display.chroma_key.feather 不能小于 0");
        }

        Ok(())
    }
}

impl BrightnessAdjustConfig {
    pub fn validate(&self) -> Result<()> {
        if !self.subject_boost.is_finite() || self.subject_boost < 0.0 {
            bail!("display.brightness_adjust.subject_boost 必须为非负有限数");
        }

        if !self.background_suppress.is_finite() || !(0.0..=1.0).contains(&self.background_suppress)
        {
            bail!("display.brightness_adjust.background_suppress 必须在 0.0 到 1.0 之间");
        }

        if !(0..=255).contains(&self.threshold) {
            bail!("display.brightness_adjust.threshold 必须在 0 到 255 之间");
        }

        Ok(())
    }
}

impl VideoItem {
    pub fn validate(&self, section: &str) -> Result<()> {
        if self.id.trim().is_empty() {
            bail!("{section}.id 不能为空");
        }

        if self.path.trim().is_empty() {
            bail!("{section}.path 不能为空");
        }

        if self.loop_count <= 0 {
            bail!("{section}.loop_count 必须大于 0");
        }

        if let Some(duration) = self.duration {
            if !duration.is_finite() || duration <= 0.0 {
                bail!("{section}.duration 必须为正有限数");
            }
        }

        if let Some([min, max]) = self.random_loop_range {
            if min <= 0 || max <= 0 {
                bail!("{section}.random_loop_range 值必须大于 0");
            }
            if min > max {
                bail!("{section}.random_loop_range[0] 不能大于 random_loop_range[1]");
            }
        }

        Ok(())
    }
}

impl TransitionConfig {
    pub fn validate(&self) -> Result<()> {
        if !self.duration.is_finite() || self.duration < 0.0 {
            bail!("transition.duration 必须为非负有限数");
        }

        if self.enabled && self.transition_type != TransitionType::None && self.duration <= 0.0 {
            bail!("transition 启用且 type 非 none 时，duration 必须大于 0");
        }

        Ok(())
    }
}

impl ScenesConfig {
    pub fn validate(&self, playlist_index: &HashSet<&str>) -> Result<()> {
        self.rest.validate_items("scenes.rest")?;
        self.active.validate_items("scenes.active")?;
        self.sleep.validate_items("scenes.sleep")?;
        self.interact.validate_items("scenes.interact")?;

        let Some(state_machine) = &self.state_machine else {
            return Ok(());
        };

        if state_machine.initial_state.trim().is_empty() {
            bail!("scenes.state_machine.initial_state 不能为空");
        }

        if !state_machine
            .states
            .contains_key(&state_machine.initial_state)
        {
            bail!(
                "scenes.state_machine.initial_state '{}' 不存在于 states 中",
                state_machine.initial_state
            );
        }

        let mut has_free_mode = false;
        for (state_id, state) in &state_machine.states {
            if state.mode == StateMode::FreeMode {
                has_free_mode = true;
            }

            for (step_index, step) in state.sequence.iter().enumerate() {
                if step.video_id.trim().is_empty() {
                    bail!(
                        "scenes.state_machine.states['{state_id}'].sequence[{step_index}].video_id 不能为空"
                    );
                }
                if !playlist_index.contains(step.video_id.as_str()) {
                    bail!(
                        "scenes.state_machine.states['{state_id}'].sequence[{step_index}] 引用的 video_id '{}' 不存在于 playlist 中",
                        step.video_id
                    );
                }
            }

            if let Some(next_state) = &state.next_state {
                if next_state.trim().is_empty() {
                    bail!("scenes.state_machine.states['{state_id}'].next_state 不能为空");
                }
                if !state_machine.states.contains_key(next_state) {
                    bail!(
                        "scenes.state_machine.states['{state_id}'].next_state '{}' 不存在",
                        next_state
                    );
                }
            }

            if let Some(next_states) = &state.next_states {
                for (entry_index, entry) in next_states.iter().enumerate() {
                    if entry.state.trim().is_empty() {
                        bail!(
                            "scenes.state_machine.states['{state_id}'].next_states[{entry_index}].state 不能为空"
                        );
                    }
                    if !state_machine.states.contains_key(&entry.state) {
                        bail!(
                            "scenes.state_machine.states['{state_id}'].next_states[{entry_index}] 的目标状态 '{}' 不存在",
                            entry.state
                        );
                    }
                    if entry.weight <= 0.0 {
                        bail!(
                            "scenes.state_machine.states['{state_id}'].next_states[{entry_index}].weight 必须大于 0"
                        );
                    }
                }
            }

            for (transition_index, transition) in state.transitions.iter().enumerate() {
                if transition.target_state.trim().is_empty() {
                    bail!(
                        "scenes.state_machine.states['{state_id}'].transitions[{transition_index}].target_state 不能为空"
                    );
                }
                if !state_machine.states.contains_key(&transition.target_state) {
                    bail!(
                        "scenes.state_machine.states['{state_id}'].transitions[{transition_index}] 的 target_state '{}' 不存在",
                        transition.target_state
                    );
                }
            }
        }

        if !has_free_mode {
            bail!("scenes.state_machine 至少需要一个 FreeMode 状态");
        }

        Ok(())
    }
}

impl RemoteControlConfig {
    pub fn validate(&self) -> Result<()> {
        if self.enabled && self.host.trim().is_empty() {
            bail!("remote_control.enabled 为 true 时，host 不能为空");
        }

        if self.enabled && self.port == 0 {
            bail!("remote_control.enabled 为 true 时，port 不能为 0");
        }

        Ok(())
    }
}

trait ValidateVideoItems {
    fn validate_items(&self, section: &str) -> Result<()>;
}

impl ValidateVideoItems for [VideoItem] {
    fn validate_items(&self, section: &str) -> Result<()> {
        for (index, item) in self.iter().enumerate() {
            item.validate(&format!("{section}[{index}]"))?;
        }
        Ok(())
    }
}

fn validate_hsv_component(field: &str, value: i32, min: i32, max: i32) -> Result<()> {
    if !(min..=max).contains(&value) {
        bail!("{field} 必须在 {min} 到 {max} 之间");
    }
    Ok(())
}
