use crate::activity::{append_activity, cap_activity, DEFAULT_ACTIVITY_LIMIT};
use crate::models::{
    ActivityEntry, ComponentStatus, LocalSingBoxConfig, Profile, SubscriptionCache, Target,
};
use serde::{de::DeserializeOwned, Serialize};
use std::fs;
use std::io::{self, ErrorKind};
use std::path::{Path, PathBuf};

pub fn default_config_root() -> PathBuf {
    PathBuf::from(r"C:\ProgramData\VpnProxy")
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StoragePaths {
    pub root: PathBuf,
    pub config_dir: PathBuf,
    pub state_dir: PathBuf,
    pub generated_dir: PathBuf,
    pub profiles_file: PathBuf,
    pub targets_file: PathBuf,
    pub components_file: PathBuf,
    pub local_singbox_file: PathBuf,
    pub singbox_subscription_cache_file: PathBuf,
    pub activity_file: PathBuf,
}

impl StoragePaths {
    pub fn new(root: impl Into<PathBuf>) -> Self {
        let root = root.into();
        let config_dir = root.join("config");
        let state_dir = root.join("state");
        let generated_dir = root.join("generated");

        Self {
            root,
            profiles_file: config_dir.join("profiles.json"),
            targets_file: config_dir.join("targets.json"),
            components_file: config_dir.join("components.json"),
            local_singbox_file: config_dir.join("local-singbox.json"),
            singbox_subscription_cache_file: state_dir.join("singbox-subscription-cache.json"),
            activity_file: state_dir.join("activity.json"),
            config_dir,
            state_dir,
            generated_dir,
        }
    }
}

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

#[derive(Debug, Clone)]
pub struct JsonStorage {
    paths: StoragePaths,
    activity_limit: usize,
}

impl JsonStorage {
    pub fn new(root: impl Into<PathBuf>) -> Self {
        Self::with_activity_limit(root, DEFAULT_ACTIVITY_LIMIT)
    }

    pub fn with_activity_limit(root: impl Into<PathBuf>, activity_limit: usize) -> Self {
        Self {
            paths: StoragePaths::new(root),
            activity_limit,
        }
    }

    pub fn paths(&self) -> &StoragePaths {
        &self.paths
    }

    pub fn ensure_dirs(&self) -> io::Result<()> {
        fs::create_dir_all(&self.paths.config_dir)?;
        fs::create_dir_all(&self.paths.state_dir)?;
        fs::create_dir_all(&self.paths.generated_dir)?;
        Ok(())
    }

    pub fn read_profiles(&self) -> io::Result<Vec<Profile>> {
        self.read_json_or_default(&self.paths.profiles_file)
    }

    pub fn write_profiles(&self, profiles: &[Profile]) -> io::Result<()> {
        self.write_json(&self.paths.profiles_file, profiles)
    }

    pub fn read_targets(&self) -> io::Result<Vec<Target>> {
        self.read_json_or_default(&self.paths.targets_file)
    }

    pub fn write_targets(&self, targets: &[Target]) -> io::Result<()> {
        self.write_json(&self.paths.targets_file, targets)
    }

    pub fn read_components(&self) -> io::Result<Vec<ComponentStatus>> {
        self.read_json_or_default(&self.paths.components_file)
    }

    pub fn write_components(&self, components: &[ComponentStatus]) -> io::Result<()> {
        self.write_json(&self.paths.components_file, components)
    }

    pub fn read_local_singbox_config(&self) -> io::Result<LocalSingBoxConfig> {
        self.read_json_or_default(&self.paths.local_singbox_file)
    }

    pub fn write_local_singbox_config(&self, config: &LocalSingBoxConfig) -> io::Result<()> {
        self.write_json(&self.paths.local_singbox_file, config)
    }

    pub fn read_singbox_subscription_cache(&self) -> io::Result<Option<SubscriptionCache>> {
        self.read_optional_json(&self.paths.singbox_subscription_cache_file)
    }

    pub fn write_singbox_subscription_cache(&self, cache: &SubscriptionCache) -> io::Result<()> {
        self.write_json(&self.paths.singbox_subscription_cache_file, cache)
    }

    pub fn remove_singbox_subscription_cache(&self) -> io::Result<()> {
        match fs::remove_file(&self.paths.singbox_subscription_cache_file) {
            Ok(()) => Ok(()),
            Err(error) if error.kind() == ErrorKind::NotFound => Ok(()),
            Err(error) => Err(error),
        }
    }

    pub fn read_activity(&self) -> io::Result<Vec<ActivityEntry>> {
        let entries = self.read_json_or_default(&self.paths.activity_file)?;
        Ok(cap_activity(entries, self.activity_limit))
    }

    pub fn write_activity(&self, entries: &[ActivityEntry]) -> io::Result<()> {
        let entries = cap_activity(entries.to_vec(), self.activity_limit);
        self.write_json(&self.paths.activity_file, &entries)
    }

    pub fn append_activity(&self, entry: ActivityEntry) -> io::Result<Vec<ActivityEntry>> {
        let entries = self.read_activity()?;
        let entries = append_activity(entries, entry, self.activity_limit);
        self.write_json(&self.paths.activity_file, &entries)?;
        Ok(entries)
    }

    fn read_json_or_default<T>(&self, path: &Path) -> io::Result<T>
    where
        T: DeserializeOwned + Default,
    {
        match fs::read_to_string(path) {
            Ok(contents) => match serde_json::from_str(&contents) {
                Ok(value) => Ok(value),
                Err(_) => Ok(T::default()),
            },
            Err(error) if error.kind() == ErrorKind::NotFound => Ok(T::default()),
            Err(error) => Err(error),
        }
    }

    fn write_json<T>(&self, path: &Path, value: &T) -> io::Result<()>
    where
        T: Serialize + ?Sized,
    {
        let contents = serde_json::to_vec_pretty(value)
            .map_err(|error| io::Error::new(ErrorKind::InvalidData, error))?;
        write_atomic(path, &contents)
    }

    fn read_optional_json<T>(&self, path: &Path) -> io::Result<Option<T>>
    where
        T: DeserializeOwned,
    {
        match fs::read_to_string(path) {
            Ok(contents) => Ok(serde_json::from_str(&contents).ok()),
            Err(error) if error.kind() == ErrorKind::NotFound => Ok(None),
            Err(error) => Err(error),
        }
    }
}

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

pub fn backup_path(path: &Path) -> PathBuf {
    sibling_with_suffix(path, "bak")
}

fn temp_path(path: &Path) -> PathBuf {
    sibling_with_suffix(path, "tmp")
}

fn sibling_with_suffix(path: &Path, suffix: &str) -> PathBuf {
    let file_name = path
        .file_name()
        .and_then(|value| value.to_str())
        .unwrap_or("storage.json");

    path.with_file_name(format!("{file_name}.{suffix}"))
}

fn write_atomic(path: &Path, contents: &[u8]) -> io::Result<()> {
    if let Some(parent) = path.parent() {
        fs::create_dir_all(parent)?;
    }

    let temp_path = temp_path(path);
    fs::write(&temp_path, contents)?;

    if path.exists() {
        fs::copy(path, backup_path(path))?;
        fs::remove_file(path)?;
    }

    match fs::rename(&temp_path, path) {
        Ok(()) => Ok(()),
        Err(error) => {
            let _ = fs::remove_file(&temp_path);
            Err(error)
        }
    }
}
