use std::path::{Path, PathBuf};

use serde::{Deserialize, Serialize};

use crate::config::AppConfig;
use crate::error::{PixstripError, Result};
use crate::preset::Preset;

fn default_config_dir() -> PathBuf {
    dirs::config_dir()
        .or_else(|| dirs::home_dir().map(|h| h.join(".config")))
        .unwrap_or_else(std::env::temp_dir)
        .join("pixstrip")
}

/// Write to a temporary file then rename, for crash safety.
pub fn atomic_write(path: &Path, contents: &str) -> std::io::Result<()> {
    let tmp = path.with_extension("tmp");
    std::fs::write(&tmp, contents)?;
    std::fs::rename(&tmp, path)?;
    Ok(())
}

fn sanitize_filename(name: &str) -> String {
    name.chars()
        .map(|c| match c {
            '/' | '\\' | '<' | '>' | ':' | '"' | '|' | '?' | '*' => '_',
            _ => c,
        })
        .collect()
}

// --- Preset Store ---

pub struct PresetStore {
    presets_dir: PathBuf,
}

impl PresetStore {
    pub fn new() -> Self {
        Self {
            presets_dir: default_config_dir().join("presets"),
        }
    }

    pub fn with_base_dir(base: &Path) -> Self {
        Self {
            presets_dir: base.join("presets"),
        }
    }

    fn ensure_dir(&self) -> Result<()> {
        std::fs::create_dir_all(&self.presets_dir).map_err(PixstripError::Io)
    }

    fn preset_path(&self, name: &str) -> PathBuf {
        let filename = format!("{}.json", sanitize_filename(name));
        self.presets_dir.join(filename)
    }

    pub fn save(&self, preset: &Preset) -> Result<()> {
        self.ensure_dir()?;
        let path = self.preset_path(&preset.name);
        let json = serde_json::to_string_pretty(preset)
            .map_err(|e| PixstripError::Preset(e.to_string()))?;
        atomic_write(&path, &json).map_err(PixstripError::Io)
    }

    pub fn load(&self, name: &str) -> Result<Preset> {
        let path = self.preset_path(name);
        if !path.exists() {
            return Err(PixstripError::Preset(format!(
                "Preset '{}' not found",
                name
            )));
        }
        let data = std::fs::read_to_string(&path).map_err(PixstripError::Io)?;
        serde_json::from_str(&data).map_err(|e| PixstripError::Preset(e.to_string()))
    }

    pub fn list(&self) -> Result<Vec<Preset>> {
        if !self.presets_dir.exists() {
            return Ok(Vec::new());
        }

        let mut presets = Vec::new();
        for entry in std::fs::read_dir(&self.presets_dir).map_err(PixstripError::Io)? {
            let entry = entry.map_err(PixstripError::Io)?;
            let path = entry.path();
            if path.extension().and_then(|e| e.to_str()) == Some("json") {
                let data = std::fs::read_to_string(&path).map_err(PixstripError::Io)?;
                if let Ok(preset) = serde_json::from_str::<Preset>(&data) {
                    presets.push(preset);
                }
            }
        }
        presets.sort_by(|a, b| a.name.cmp(&b.name));
        Ok(presets)
    }

    pub fn delete(&self, name: &str) -> Result<()> {
        let path = self.preset_path(name);
        if !path.exists() {
            return Err(PixstripError::Preset(format!(
                "Preset '{}' not found",
                name
            )));
        }
        std::fs::remove_file(&path).map_err(PixstripError::Io)
    }

    pub fn export_to_file(&self, preset: &Preset, path: &Path) -> Result<()> {
        let json = serde_json::to_string_pretty(preset)
            .map_err(|e| PixstripError::Preset(e.to_string()))?;
        atomic_write(path, &json).map_err(PixstripError::Io)
    }

    pub fn import_from_file(&self, path: &Path) -> Result<Preset> {
        let data = std::fs::read_to_string(path).map_err(PixstripError::Io)?;
        let mut preset: Preset =
            serde_json::from_str(&data).map_err(|e| PixstripError::Preset(e.to_string()))?;
        preset.is_custom = true;
        Ok(preset)
    }
}

impl Default for PresetStore {
    fn default() -> Self {
        Self::new()
    }
}

// --- Config Store ---

pub struct ConfigStore {
    config_path: PathBuf,
}

impl ConfigStore {
    pub fn new() -> Self {
        Self {
            config_path: default_config_dir().join("config.json"),
        }
    }

    pub fn with_base_dir(base: &Path) -> Self {
        Self {
            config_path: base.join("config.json"),
        }
    }

    pub fn save(&self, config: &AppConfig) -> Result<()> {
        if let Some(parent) = self.config_path.parent() {
            std::fs::create_dir_all(parent).map_err(PixstripError::Io)?;
        }
        let json = serde_json::to_string_pretty(config)
            .map_err(|e| PixstripError::Config(e.to_string()))?;
        atomic_write(&self.config_path, &json).map_err(PixstripError::Io)
    }

    pub fn load(&self) -> Result<AppConfig> {
        if !self.config_path.exists() {
            return Ok(AppConfig::default());
        }
        let data = std::fs::read_to_string(&self.config_path).map_err(PixstripError::Io)?;
        serde_json::from_str(&data).map_err(|e| PixstripError::Config(e.to_string()))
    }
}

impl Default for ConfigStore {
    fn default() -> Self {
        Self::new()
    }
}

// --- Session Store ---

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default)]
pub struct SessionState {
    pub last_input_dir: Option<String>,
    pub last_output_dir: Option<String>,
    pub last_preset_name: Option<String>,
    pub current_step: u32,
    pub window_width: Option<i32>,
    pub window_height: Option<i32>,
    pub window_maximized: bool,
    // Last-used wizard settings
    pub resize_enabled: Option<bool>,
    pub resize_width: Option<u32>,
    pub resize_height: Option<u32>,
    pub adjustments_enabled: Option<bool>,
    pub convert_enabled: Option<bool>,
    pub convert_format: Option<String>,
    pub compress_enabled: Option<bool>,
    pub quality_preset: Option<String>,
    pub metadata_enabled: Option<bool>,
    pub metadata_mode: Option<String>,
    pub watermark_enabled: Option<bool>,
    pub rename_enabled: Option<bool>,
    pub last_seen_version: Option<String>,
    pub expanded_sections: std::collections::HashMap<String, bool>,
}

pub struct SessionStore {
    session_path: PathBuf,
}

impl SessionStore {
    pub fn new() -> Self {
        Self {
            session_path: default_config_dir().join("session.json"),
        }
    }

    pub fn with_base_dir(base: &Path) -> Self {
        Self {
            session_path: base.join("session.json"),
        }
    }

    pub fn save(&self, state: &SessionState) -> Result<()> {
        if let Some(parent) = self.session_path.parent() {
            std::fs::create_dir_all(parent).map_err(PixstripError::Io)?;
        }
        let json = serde_json::to_string_pretty(state)
            .map_err(|e| PixstripError::Config(e.to_string()))?;
        atomic_write(&self.session_path, &json).map_err(PixstripError::Io)
    }

    pub fn load(&self) -> Result<SessionState> {
        if !self.session_path.exists() {
            return Ok(SessionState::default());
        }
        let data = std::fs::read_to_string(&self.session_path).map_err(PixstripError::Io)?;
        serde_json::from_str(&data).map_err(|e| PixstripError::Config(e.to_string()))
    }
}

impl Default for SessionStore {
    fn default() -> Self {
        Self::new()
    }
}

// --- History Store ---

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HistoryEntry {
    pub timestamp: String,
    pub input_dir: String,
    pub output_dir: String,
    pub preset_name: Option<String>,
    pub total: usize,
    pub succeeded: usize,
    pub failed: usize,
    pub total_input_bytes: u64,
    pub total_output_bytes: u64,
    pub elapsed_ms: u64,
    pub output_files: Vec<String>,
}

pub struct HistoryStore {
    history_path: PathBuf,
}

impl HistoryStore {
    pub fn new() -> Self {
        Self {
            history_path: default_config_dir().join("history.json"),
        }
    }

    pub fn with_base_dir(base: &Path) -> Self {
        Self {
            history_path: base.join("history.json"),
        }
    }

    pub fn add(&self, entry: HistoryEntry, max_entries: usize, max_days: u32) -> Result<()> {
        let mut entries = self.list()?;
        entries.push(entry);
        self.write_all(&entries)?;
        self.prune(max_entries, max_days)
    }

    pub fn prune(&self, max_entries: usize, max_days: u32) -> Result<()> {
        let mut entries = self.list()?;
        if entries.is_empty() {
            return Ok(());
        }

        let now_secs = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
        let cutoff_secs = now_secs.saturating_sub(max_days as u64 * 86400);

        // Remove entries older than max_days (keep entries with unparseable timestamps)
        entries.retain(|e| {
            e.timestamp.parse::<u64>().map_or(true, |ts| ts >= cutoff_secs)
        });

        // Trim to max_entries (keep the most recent)
        if entries.len() > max_entries {
            let start = entries.len() - max_entries;
            entries = entries.split_off(start);
        }

        self.write_all(&entries)
    }

    pub fn list(&self) -> Result<Vec<HistoryEntry>> {
        if !self.history_path.exists() {
            return Ok(Vec::new());
        }
        let data = std::fs::read_to_string(&self.history_path).map_err(PixstripError::Io)?;
        if data.trim().is_empty() {
            return Ok(Vec::new());
        }
        serde_json::from_str(&data).map_err(|e| PixstripError::Config(e.to_string()))
    }

    pub fn clear(&self) -> Result<()> {
        self.write_all(&Vec::<HistoryEntry>::new())
    }

    pub fn write_all(&self, entries: &[HistoryEntry]) -> Result<()> {
        if let Some(parent) = self.history_path.parent() {
            std::fs::create_dir_all(parent).map_err(PixstripError::Io)?;
        }
        let json = serde_json::to_string_pretty(entries)
            .map_err(|e| PixstripError::Config(e.to_string()))?;
        atomic_write(&self.history_path, &json).map_err(PixstripError::Io)
    }
}

impl Default for HistoryStore {
    fn default() -> Self {
        Self::new()
    }
}
