use std::{
    fmt::{Debug, Display},
    path::PathBuf,
};

use anyhow::{Context, anyhow};
use serde::{Deserialize, Serialize};

use crate::{
    fixedptc::Fpt,
    params::{
        ALL_MODES, AccelMode, CommonParamArgs, LinearParamArgs, NaturalParamArgs, Param,
        SynchronousParamArgs, format_param_value, validate_param_value,
    },
};

pub trait ParamStore: Debug {
    fn set(&mut self, param: Param, value: f64) -> anyhow::Result<()>;
    fn get(&self, param: Param) -> anyhow::Result<Fpt>;

    fn set_current_accel_mode(&mut self, mode: AccelMode) -> anyhow::Result<()>;
    fn get_current_accel_mode(&self) -> anyhow::Result<AccelMode>;
}

const CONFIG_ENV_VAR: &str = "GSF_CONFIG_PATH";

#[derive(Debug, Clone, Serialize, Deserialize)]
struct StoredConfig {
    mode: u8,
    sens_mult: f64,
    yx_ratio: f64,
    input_dpi: f64,
    angle_rotation: f64,
    accel: f64,
    offset_linear: f64,
    output_cap: f64,
    decay_rate: f64,
    offset_natural: f64,
    limit: f64,
    gamma: f64,
    smooth: f64,
    motivity: f64,
    sync_speed: f64,
}

impl Default for StoredConfig {
    fn default() -> Self {
        Self {
            mode: AccelMode::Linear as u8,
            sens_mult: 1.0,
            yx_ratio: 1.0,
            input_dpi: 1000.0,
            angle_rotation: 0.0,
            accel: 0.3,
            offset_linear: 2.0,
            output_cap: 2.0,
            decay_rate: 0.1,
            offset_natural: 0.0,
            limit: 1.5,
            gamma: 1.0,
            smooth: 0.5,
            motivity: 1.5,
            sync_speed: 5.0,
        }
    }
}

#[derive(Debug)]
pub struct SysFsStore;

impl ParamStore for SysFsStore {
    fn set(&mut self, param: Param, value: f64) -> anyhow::Result<()> {
        validate_param_value(param, value)?;

        let mut cfg = load_or_init_config()?;
        set_param_on_config(&mut cfg, param, value);
        save_config(&cfg)?;
        Ok(())
    }

    fn get(&self, param: Param) -> anyhow::Result<Fpt> {
        let cfg = load_or_init_config()?;
        Ok(get_param_from_config(&cfg, param).into())
    }

    fn set_current_accel_mode(&mut self, mode: AccelMode) -> anyhow::Result<()> {
        let mut cfg = load_or_init_config()?;
        cfg.mode = mode as u8;
        save_config(&cfg)?;
        Ok(())
    }

    fn get_current_accel_mode(&self) -> anyhow::Result<AccelMode> {
        let cfg = load_or_init_config()?;
        let idx = (cfg.mode as usize) % ALL_MODES.len();
        Ok(ALL_MODES[idx])
    }
}

impl SysFsStore {
    pub fn set_all_common(&mut self, args: CommonParamArgs) -> anyhow::Result<()> {
        let CommonParamArgs {
            sens_mult,
            yx_ratio,
            input_dpi,
            angle_rotation,
        } = args;

        self.set(Param::SensMult, sens_mult)?;
        self.set(Param::YxRatio, yx_ratio)?;
        self.set(Param::InputDpi, input_dpi)?;
        self.set(Param::AngleRotation, angle_rotation)?;

        Ok(())
    }

    pub fn set_all_linear(&mut self, args: LinearParamArgs) -> anyhow::Result<()> {
        let LinearParamArgs {
            accel,
            offset_linear,
            output_cap,
        } = args;

        self.set(Param::Accel, accel)?;
        self.set(Param::OffsetLinear, offset_linear)?;
        self.set(Param::OutputCap, output_cap)?;

        Ok(())
    }

    pub fn set_all_natural(&mut self, args: NaturalParamArgs) -> anyhow::Result<()> {
        let NaturalParamArgs {
            decay_rate,
            limit,
            offset_natural,
        } = args;

        self.set(Param::DecayRate, decay_rate)?;
        self.set(Param::OffsetNatural, offset_natural)?;
        self.set(Param::Limit, limit)?;

        Ok(())
    }

    pub fn set_all_synchronous(&mut self, args: SynchronousParamArgs) -> anyhow::Result<()> {
        let SynchronousParamArgs {
            gamma,
            smooth,
            motivity,
            sync_speed,
        } = args;

        self.set(Param::Gamma, gamma)?;
        self.set(Param::Smooth, smooth)?;
        self.set(Param::Motivity, motivity)?;
        self.set(Param::SyncSpeed, sync_speed)?;

        Ok(())
    }
}

fn config_path() -> anyhow::Result<PathBuf> {
    if let Ok(p) = std::env::var(CONFIG_ENV_VAR) {
        return Ok(PathBuf::from(p));
    }

    // If daemon is launched with sudo, prefer caller's home so CLI/TUI and daemon share config.
    if let Ok(sudo_user) = std::env::var("SUDO_USER")
        && !sudo_user.is_empty()
        && sudo_user != "root"
    {
        let candidate = PathBuf::from("/home")
            .join(sudo_user)
            .join(".config")
            .join("gsf")
            .join("config.json");
        return Ok(candidate);
    }

    let home = std::env::var("HOME").context("HOME is not set")?;
    Ok(PathBuf::from(home)
        .join(".config")
        .join("gsf")
        .join("config.json"))
}

fn load_or_init_config() -> anyhow::Result<StoredConfig> {
    let path = config_path()?;

    if !path.exists() {
        if let Some(parent) = path.parent() {
            std::fs::create_dir_all(parent)
                .with_context(|| anyhow!("failed to create config directory {}", parent.display()))?;
        }
        let cfg = StoredConfig::default();
        save_config(&cfg)?;
        return Ok(cfg);
    }

    let content = std::fs::read_to_string(&path)
        .with_context(|| anyhow!("failed to read config file {}", path.display()))?;
    let cfg: StoredConfig =
        serde_json::from_str(&content).context("failed to parse gsf config JSON")?;
    Ok(cfg)
}

fn save_config(cfg: &StoredConfig) -> anyhow::Result<()> {
    let path = config_path()?;
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)
            .with_context(|| anyhow!("failed to create config directory {}", parent.display()))?;
    }

    let content = serde_json::to_string_pretty(cfg).context("failed to serialize config")?;
    std::fs::write(&path, content)
        .with_context(|| anyhow!("failed to write config {}", path.display()))?;

    Ok(())
}

fn get_param_from_config(cfg: &StoredConfig, param: Param) -> f64 {
    match param {
        Param::SensMult => cfg.sens_mult,
        Param::YxRatio => cfg.yx_ratio,
        Param::InputDpi => cfg.input_dpi,
        Param::AngleRotation => cfg.angle_rotation,
        Param::Accel => cfg.accel,
        Param::OffsetLinear => cfg.offset_linear,
        Param::OutputCap => cfg.output_cap,
        Param::DecayRate => cfg.decay_rate,
        Param::OffsetNatural => cfg.offset_natural,
        Param::Limit => cfg.limit,
        Param::Gamma => cfg.gamma,
        Param::Smooth => cfg.smooth,
        Param::Motivity => cfg.motivity,
        Param::SyncSpeed => cfg.sync_speed,
    }
}

fn set_param_on_config(cfg: &mut StoredConfig, param: Param, value: f64) {
    match param {
        Param::SensMult => cfg.sens_mult = value,
        Param::YxRatio => cfg.yx_ratio = value,
        Param::InputDpi => cfg.input_dpi = value,
        Param::AngleRotation => cfg.angle_rotation = value,
        Param::Accel => cfg.accel = value,
        Param::OffsetLinear => cfg.offset_linear = value,
        Param::OutputCap => cfg.output_cap = value,
        Param::DecayRate => cfg.decay_rate = value,
        Param::OffsetNatural => cfg.offset_natural = value,
        Param::Limit => cfg.limit = value,
        Param::Gamma => cfg.gamma = value,
        Param::Smooth => cfg.smooth = value,
        Param::Motivity => cfg.motivity = value,
        Param::SyncSpeed => cfg.sync_speed = value,
    }
}

impl Display for Fpt {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(&format_param_value(f64::from(*self)))
    }
}
