use percent_encoding::percent_decode_str;
use serde::{Deserialize, Serialize};
use serde_json::Value;

pub const DEFAULT_LOCAL_SINGBOX_LISTEN_HOST: &str = "127.0.0.1";
pub const DEFAULT_LOCAL_SINGBOX_LISTEN_PORT: u16 = 1080;
pub const DEFAULT_LOCAL_SINGBOX_SERVICE_NAME: &str = "ProxyWardenSingBox";
pub const DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT: &str = r"C:\Program Files\ProxyWarden\sing-box";

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Protocol {
    Tcp,
    Udp,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ProfileItemType {
    Process,
    Folder,
    Exe,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum TargetKind {
    Local,
    External,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ProxyProtocol {
    Socks5,
    Http,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum ComponentId {
    ControlApp,
    Proxyfier,
    Singbox,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum ComponentState {
    Installed,
    Missing,
    Stopped,
    Running,
    Error,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProfileItemInput {
    #[serde(rename = "type")]
    pub item_type: String,
    pub value: String,
    #[serde(default)]
    pub recursive: Option<bool>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProfileInput {
    pub id: Option<String>,
    pub name: String,
    #[serde(default = "default_enabled")]
    pub enabled: bool,
    #[serde(default = "default_target_id")]
    pub target_id: String,
    #[serde(default = "default_protocols")]
    pub protocols: Vec<String>,
    #[serde(default)]
    pub items: Vec<ProfileItemInput>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProfileItem {
    #[serde(rename = "type")]
    pub item_type: ProfileItemType,
    pub value: String,
    pub recursive: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Profile {
    pub id: String,
    pub name: String,
    pub enabled: bool,
    pub target_id: String,
    pub protocols: Vec<Protocol>,
    pub items: Vec<ProfileItem>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TargetInput {
    pub id: Option<String>,
    pub name: String,
    #[serde(default = "default_target_kind")]
    pub kind: String,
    #[serde(default = "default_proxy_protocol")]
    pub protocol: String,
    pub host: String,
    pub port: u32,
    #[serde(default)]
    pub requires_component: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Target {
    pub id: String,
    pub name: String,
    pub kind: TargetKind,
    pub protocol: ProxyProtocol,
    pub host: String,
    pub port: u16,
    pub requires_component: Option<ComponentId>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ComponentStatus {
    pub id: ComponentId,
    pub name: String,
    pub state: ComponentState,
    pub installed: bool,
    pub running: bool,
    pub version: Option<String>,
    pub path: Option<String>,
    #[serde(default)]
    pub problems: Vec<String>,
    #[serde(default)]
    pub actions: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct LocalSingBoxConfig {
    #[serde(default)]
    pub subscription_url: Option<String>,
    #[serde(default)]
    pub device_hwid: Option<String>,
    #[serde(default)]
    pub selected_server_tag: Option<String>,
    #[serde(default = "default_local_singbox_listen_host")]
    pub listen_host: String,
    #[serde(default = "default_local_singbox_listen_port")]
    pub listen_port: u16,
    #[serde(default = "default_local_singbox_service_name")]
    pub service_name: String,
    #[serde(default = "default_local_singbox_install_root")]
    pub install_root: String,
    #[serde(default)]
    pub updated_at: Option<String>,
}

impl LocalSingBoxConfig {
    pub fn subscription_display_url(&self) -> Option<String> {
        self.subscription_url
            .as_deref()
            .map(redact_subscription_url)
    }

    pub fn normalize_percent_encoded_tags(&mut self) {
        if let Some(selected_server_tag) = self.selected_server_tag.as_mut() {
            *selected_server_tag = decode_percent_encoded_utf8(selected_server_tag);
        }
    }
}

impl Default for LocalSingBoxConfig {
    fn default() -> Self {
        Self {
            subscription_url: None,
            device_hwid: None,
            selected_server_tag: None,
            listen_host: default_local_singbox_listen_host(),
            listen_port: default_local_singbox_listen_port(),
            service_name: default_local_singbox_service_name(),
            install_root: default_local_singbox_install_root(),
            updated_at: None,
        }
    }
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SubscriptionCache {
    pub config: serde_json::Value,
    #[serde(default)]
    pub servers: Vec<SubscriptionServer>,
    #[serde(default)]
    pub user_info: serde_json::Map<String, serde_json::Value>,
    pub fetched_at: String,
}

impl SubscriptionCache {
    pub fn normalize_percent_encoded_tags(&mut self) {
        for server in &mut self.servers {
            server.tag = decode_percent_encoded_utf8(&server.tag);
        }

        let Some(outbounds) = self
            .config
            .get_mut("outbounds")
            .and_then(Value::as_array_mut)
        else {
            return;
        };

        for outbound in outbounds {
            let Some(decoded_tag) = outbound
                .get("tag")
                .and_then(Value::as_str)
                .map(decode_percent_encoded_utf8)
            else {
                continue;
            };

            if let Some(object) = outbound.as_object_mut() {
                object.insert("tag".to_string(), Value::String(decoded_tag));
            }
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SubscriptionServer {
    pub tag: String,
    #[serde(rename = "type")]
    pub server_type: String,
    pub server: String,
    pub server_port: u16,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ActivityEntry {
    pub id: String,
    pub at: String,
    pub level: ActivityLevel,
    pub title: String,
    pub message: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ActivityLevel {
    Info,
    Warning,
    Error,
    Success,
}

fn default_enabled() -> bool {
    true
}

fn default_target_id() -> String {
    "local-singbox".to_string()
}

fn default_protocols() -> Vec<String> {
    vec!["TCP".to_string(), "UDP".to_string()]
}

fn default_target_kind() -> String {
    "external".to_string()
}

fn default_proxy_protocol() -> String {
    "socks5".to_string()
}

fn default_local_singbox_listen_host() -> String {
    DEFAULT_LOCAL_SINGBOX_LISTEN_HOST.to_string()
}

fn default_local_singbox_listen_port() -> u16 {
    DEFAULT_LOCAL_SINGBOX_LISTEN_PORT
}

fn default_local_singbox_service_name() -> String {
    DEFAULT_LOCAL_SINGBOX_SERVICE_NAME.to_string()
}

fn default_local_singbox_install_root() -> String {
    DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT.to_string()
}

pub fn redact_subscription_url(raw_url: &str) -> String {
    let trimmed = raw_url.trim();
    if trimmed.is_empty() {
        return String::new();
    }

    match trimmed.split_once("://") {
        Some((scheme, rest)) => {
            let host = rest
                .split(['/', '?', '#'])
                .next()
                .filter(|value| !value.is_empty())
                .unwrap_or("subscription");
            format!("{scheme}://{host}/...")
        }
        None => {
            let visible = trimmed.chars().take(18).collect::<String>();
            if trimmed.chars().count() <= 18 {
                "***".to_string()
            } else {
                format!("{visible}...")
            }
        }
    }
}

pub fn decode_percent_encoded_utf8(value: &str) -> String {
    percent_decode_str(value)
        .decode_utf8()
        .map(|decoded| decoded.into_owned())
        .unwrap_or_else(|_| value.to_string())
}
