use crate::models::{SubscriptionCache, SubscriptionServer};
use base64::{engine::general_purpose, Engine};
use serde_json::{json, Map, Value};
use std::time::{SystemTime, UNIX_EPOCH};
use url::Url;

const SUPPORTED_PROXY_TYPES: &[&str] = &["vless", "vmess", "trojan", "shadowsocks", "hysteria2"];

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubscriptionError {
    pub message: String,
}

impl SubscriptionError {
    fn new(message: impl Into<String>) -> Self {
        Self {
            message: message.into(),
        }
    }
}

impl std::fmt::Display for SubscriptionError {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        formatter.write_str(&self.message)
    }
}

impl std::error::Error for SubscriptionError {}

#[derive(Debug, Clone, PartialEq)]
pub struct ParsedSubscription {
    pub config: Value,
    pub servers: Vec<SubscriptionServer>,
}

pub fn parse_subscription_body(body: &str) -> Result<ParsedSubscription, SubscriptionError> {
    let config = match serde_json::from_str::<Value>(body) {
        Ok(value) => value,
        Err(_) => parse_link_subscription(body)?,
    };
    let servers = servers_from_config(&config)?;

    Ok(ParsedSubscription { config, servers })
}

pub fn parse_user_info(header_value: Option<&str>) -> Map<String, Value> {
    let mut result = Map::new();
    let Some(header_value) = header_value else {
        return result;
    };

    for part in header_value.split(';') {
        let Some((key, value)) = part.trim().split_once('=') else {
            continue;
        };
        let key = key.trim();
        if key.is_empty() {
            continue;
        }
        if let Ok(parsed) = value.trim().parse::<i64>() {
            result.insert(key.to_string(), Value::Number(parsed.into()));
        }
    }

    result
}

pub fn fetch_subscription(url: &str) -> Result<SubscriptionCache, SubscriptionError> {
    let parsed_url =
        Url::parse(url).map_err(|_| SubscriptionError::new("Invalid subscription URL"))?;
    if !matches!(parsed_url.scheme(), "http" | "https") {
        return Err(SubscriptionError::new(
            "Subscription URL must use http or https",
        ));
    }

    let response = reqwest::blocking::Client::new()
        .get(parsed_url)
        .header("user-agent", "singbox")
        .header("x-device-os", std::env::consts::OS)
        .header("x-device-model", "vpn-proxy-windows-client")
        .send()
        .map_err(|error| SubscriptionError::new(format!("Subscription request failed: {error}")))?;

    let status = response.status();
    if !status.is_success() {
        return Err(SubscriptionError::new(format!(
            "Subscription request failed: HTTP {}",
            status.as_u16()
        )));
    }

    let user_info = parse_user_info(
        response
            .headers()
            .get("subscription-userinfo")
            .and_then(|value| value.to_str().ok()),
    );
    let body = response.text().map_err(|error| {
        SubscriptionError::new(format!("Subscription body read failed: {error}"))
    })?;
    let parsed = parse_subscription_body(&body)?;

    Ok(SubscriptionCache {
        config: parsed.config,
        servers: parsed.servers,
        user_info,
        fetched_at: now_timestamp(),
    })
}

fn parse_link_subscription(body: &str) -> Result<Value, SubscriptionError> {
    let decoded = maybe_decode_base64(body);
    let links = decoded
        .lines()
        .map(str::trim)
        .filter(|line| line.starts_with("vless://"))
        .collect::<Vec<_>>();

    if links.is_empty() {
        return Err(SubscriptionError::new(
            "Subscription does not contain JSON config or VLESS links",
        ));
    }

    let outbounds = links
        .into_iter()
        .map(parse_vless_url)
        .collect::<Result<Vec<_>, _>>()?;

    Ok(json!({ "outbounds": outbounds }))
}

fn parse_vless_url(raw_url: &str) -> Result<Value, SubscriptionError> {
    if !raw_url.starts_with("vless://") {
        return Err(SubscriptionError::new("VLESS URL must start with vless://"));
    }

    let parsed = Url::parse(raw_url).map_err(|_| SubscriptionError::new("Invalid VLESS URL"))?;
    let tag = parsed.fragment().unwrap_or("vless-out").to_string();
    let uuid = parsed.username().trim().to_string();
    let server = parsed.host_str().map(str::to_string).unwrap_or_default();
    let server_port = parsed.port_or_known_default().unwrap_or(443);
    let public_key = query_value(&parsed, "pbk").unwrap_or_default();
    let short_id = query_value(&parsed, "sid").unwrap_or_default();
    let server_name = query_value(&parsed, "sni").unwrap_or_else(|| server.clone());
    let fingerprint = query_value(&parsed, "fp").unwrap_or_else(|| "chrome".to_string());
    let flow = query_value(&parsed, "flow").unwrap_or_default();

    if uuid.is_empty() || server.is_empty() {
        return Err(SubscriptionError::new(
            "VLESS URL misses uuid, host or port",
        ));
    }

    if public_key.is_empty() || short_id.is_empty() {
        return Err(SubscriptionError::new(
            "VLESS REALITY parameters pbk and sid are required",
        ));
    }

    Ok(json!({
        "type": "vless",
        "tag": tag,
        "server": server,
        "server_port": server_port,
        "uuid": uuid,
        "flow": flow,
        "tls": {
            "enabled": true,
            "server_name": server_name,
            "utls": {
                "enabled": true,
                "fingerprint": fingerprint
            },
            "reality": {
                "enabled": true,
                "public_key": public_key,
                "short_id": short_id
            }
        },
        "packet_encoding": "xudp"
    }))
}

fn servers_from_config(config: &Value) -> Result<Vec<SubscriptionServer>, SubscriptionError> {
    let servers = config
        .get("outbounds")
        .and_then(Value::as_array)
        .into_iter()
        .flatten()
        .filter_map(server_from_outbound)
        .collect::<Vec<_>>();

    if servers.is_empty() {
        return Err(SubscriptionError::new(
            "No supported proxy outbounds found in subscription",
        ));
    }

    Ok(servers)
}

fn server_from_outbound(outbound: &Value) -> Option<SubscriptionServer> {
    let server_type = outbound.get("type")?.as_str()?.to_string();
    if !SUPPORTED_PROXY_TYPES.contains(&server_type.as_str()) {
        return None;
    }

    let server = outbound
        .get("server")
        .and_then(Value::as_str)
        .unwrap_or("unknown")
        .to_string();
    let server_port = outbound
        .get("server_port")
        .and_then(Value::as_u64)
        .and_then(|value| u16::try_from(value).ok())
        .unwrap_or(443);
    let tag = outbound
        .get("tag")
        .and_then(Value::as_str)
        .map(str::to_string)
        .unwrap_or_else(|| format!("{server_type}-{server}"));

    Some(SubscriptionServer {
        tag,
        server_type,
        server,
        server_port,
    })
}

fn maybe_decode_base64(content: &str) -> String {
    let compact = content.split_whitespace().collect::<String>();
    if compact.is_empty()
        || !compact
            .chars()
            .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '+' | '/' | '=' | '-' | '_'))
    {
        return content.to_string();
    }

    for engine in [general_purpose::STANDARD, general_purpose::URL_SAFE] {
        if let Ok(decoded) = engine.decode(compact.as_bytes()) {
            if let Ok(decoded) = String::from_utf8(decoded) {
                if decoded.contains("vless://") || decoded.contains('{') {
                    return decoded;
                }
            }
        }
    }

    content.to_string()
}

fn query_value(url: &Url, key: &str) -> Option<String> {
    url.query_pairs()
        .find(|(name, _)| name == key)
        .map(|(_, value)| value.into_owned())
}

fn now_timestamp() -> String {
    let seconds = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|duration| duration.as_secs())
        .unwrap_or(0);
    format!("unix:{seconds}")
}
