use crate::error::{Error, Result};
use serde::{Deserialize, Serialize};
use std::{
    net::{Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs},
    path::PathBuf,
};

pub(crate) const TEST_TIMEOUT_SECS: u64 = 10;
pub(crate) const DEFAULT_POOL_MAX_SIZE: usize = 50;

#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
pub struct Config {
    #[serde(
        rename(deserialize = "server_settings", serialize = "server_settings"),
        skip_serializing_if = "Option::is_none"
    )]
    pub server: Option<Server>,
    #[serde(
        rename(deserialize = "client_settings", serialize = "client_settings"),
        skip_serializing_if = "Option::is_none"
    )]
    pub client: Option<Client>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub remarks: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub method: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub password: Option<String>,
    pub tunnel_path: TunnelPath,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub test_timeout_secs: Option<u64>,
    #[serde(skip)]
    pub is_server: bool,
}

#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(untagged)]
pub enum TunnelPath {
    Single(String),
    Multiple(Vec<String>),
}

impl std::fmt::Display for TunnelPath {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TunnelPath::Single(s) => write!(f, "{s}"),
            TunnelPath::Multiple(v) => {
                let mut s = String::new();
                for (i, item) in v.iter().enumerate() {
                    if i > 0 {
                        s.push(',');
                    }
                    s.push_str(item);
                }
                write!(f, "{s}")
            }
        }
    }
}

impl Default for TunnelPath {
    fn default() -> Self {
        TunnelPath::Single("/tunnel/".to_string())
    }
}

impl TunnelPath {
    pub fn is_empty(&self) -> bool {
        match self {
            TunnelPath::Single(s) => s.is_empty(),
            TunnelPath::Multiple(v) => v.is_empty(),
        }
    }

    pub fn standardize(&mut self) {
        if self.is_empty() {
            *self = TunnelPath::default();
        }
        match self {
            TunnelPath::Single(s) => {
                *s = format!("/{}/", s.trim().trim_matches('/'));
            }
            TunnelPath::Multiple(v) => {
                v.iter_mut().for_each(|s| {
                    *s = s.trim().trim_matches('/').to_string();
                    if !s.is_empty() {
                        *s = format!("/{s}/");
                    }
                });
                v.retain(|s| !s.is_empty());
            }
        }
    }

    pub fn extract(&self) -> Vec<&str> {
        match self {
            TunnelPath::Single(s) => vec![s],
            TunnelPath::Multiple(v) => v.iter().map(|s| s.as_str()).collect(),
        }
    }
}

#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
pub struct Server {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub disable_tls: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub manage_clients: Option<ManageClients>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub certfile: Option<PathBuf>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub keyfile: Option<PathBuf>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub forward_addr: Option<String>,
    pub listen_host: String,
    pub listen_port: u16,
}

#[derive(Clone, Serialize, Deserialize, Debug, Default, PartialEq, Eq)]
pub struct ManageClients {
    pub enable: Option<bool>,
    pub webapi_url: Option<String>,
    pub webapi_token: Option<String>,
    pub node_id: Option<usize>,
    #[serde(rename(deserialize = "api_update_time", serialize = "api_update_time"))]
    pub api_update_interval_secs: Option<u64>,
}

#[derive(Clone, Serialize, Deserialize, Debug, Default, Eq)]
pub struct Client {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub disable_tls: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub client_id: Option<String>,
    pub server_host: String,
    pub server_port: u16,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub server_domain: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cafile: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub dangerous_mode: Option<bool>,
    pub listen_host: String,
    pub listen_port: u16,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub listen_user: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub listen_password: Option<String>,
    #[serde(skip)]
    pub cache_dns: bool,
    #[serde(skip)]
    pub(crate) server_ip_addr: Option<SocketAddr>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pool_max_size: Option<usize>,
}

impl PartialEq for Client {
    fn eq(&self, other: &Self) -> bool {
        let dangerous_mode = self.dangerous_mode.unwrap_or(false);
        let other_dangerous_mode = other.dangerous_mode.unwrap_or(false);
        let cert_matches = if !dangerous_mode && !other_dangerous_mode {
            self.certificate_content() == other.certificate_content()
        } else {
            true
        };
        self.server_host == other.server_host
            && self.server_port == other.server_port
            && self.server_domain == other.server_domain
            && cert_matches // self.cafile == other.cafile
            && self.dangerous_mode == other.dangerous_mode
            && self.disable_tls == other.disable_tls
            && self.client_id == other.client_id
    }
}

impl Client {
    pub fn certificate_content(&self) -> Option<String> {
        self.cafile.as_ref().and_then(|cert| Self::_certificate_content(cert))
    }

    fn _certificate_content(cert: &str) -> Option<String> {
        const BEGIN_CERT: &str = "-----BEGIN CERTIFICATE-----";
        let checker = |s: &str| !s.is_empty() && s.starts_with(BEGIN_CERT) && s.len() > 100;
        if PathBuf::from(cert).exists() {
            std::fs::read_to_string(cert).ok().filter(|s| checker(s))
        } else if checker(cert) {
            Some(cert.to_string())
        } else {
            None
        }
    }

    pub fn export_certificate<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
        match self.certificate_content() {
            Some(cert) => std::fs::write(path, cert).map_err(|e| e.into()),
            None => Err(Error::from("certificate not exists")),
        }
    }
}

impl Config {
    pub fn certificate_content(&self) -> Option<String> {
        self.client.as_ref().and_then(|c| c.certificate_content())
    }

    pub fn export_certificate<P: AsRef<std::path::Path>>(&self, path: P) -> Result<()> {
        self.client.as_ref().ok_or(Error::from("no client"))?.export_certificate(path)
    }

    pub fn manage_clients(&self) -> bool {
        let f = |s: &Server| {
            let f2 = |c: &ManageClients| c.enable.unwrap_or(false);
            s.manage_clients.as_ref().map(f2).unwrap_or(false)
        };
        self.server.as_ref().map(f).unwrap_or(false)
    }

    pub fn webapi_url(&self) -> Option<String> {
        let f = |s: &Server| s.manage_clients.as_ref().map(|c| c.webapi_url.clone()).unwrap_or(None);
        self.server.as_ref().map(f).unwrap_or(None)
    }

    pub fn webapi_token(&self) -> Option<String> {
        let f = |s: &Server| {
            let f2 = |c: &ManageClients| c.webapi_token.clone();
            s.manage_clients.as_ref().map(f2).unwrap_or(None)
        };
        self.server.as_ref().map(f).unwrap_or(None)
    }

    pub fn node_id(&self) -> Option<usize> {
        let f = |s: &Server| s.manage_clients.as_ref().map(|c| c.node_id).unwrap_or(None);
        self.server.as_ref().map(f).unwrap_or(None)
    }

    pub fn api_update_interval_secs(&self) -> Option<u64> {
        let f = |s: &Server| {
            let f2 = |c: &ManageClients| c.api_update_interval_secs;
            s.manage_clients.as_ref().map(f2).unwrap_or(None)
        };
        self.server.as_ref().map(f).unwrap_or(None)
    }

    pub fn exist_server(&self) -> bool {
        self.server.is_some()
    }

    pub fn exist_client(&self) -> bool {
        self.client.is_some()
    }

    pub fn forward_addr(&self) -> Option<String> {
        if self.is_server {
            let f = |s: &Server| s.forward_addr.clone();
            let default = Some(format!("http://{}:80", Ipv4Addr::LOCALHOST));
            self.server.as_ref().map(f).unwrap_or(default)
        } else {
            None
        }
    }

    pub fn listen_addr(&self) -> Result<SocketAddr> {
        let unspec = std::net::IpAddr::from(Ipv4Addr::UNSPECIFIED);
        if self.is_server {
            let f = |s: &Server| SocketAddr::new(s.listen_host.parse().unwrap_or(unspec), s.listen_port);
            self.server.as_ref().map(f).ok_or_else(|| "Server listen address is not set".into())
        } else {
            let f = |c: &Client| SocketAddr::new(c.listen_host.parse().unwrap_or(unspec), c.listen_port);
            self.client.as_ref().map(f).ok_or_else(|| "Client listen address is not set".into())
        }
    }

    pub fn set_listen_addr(&mut self, addr: std::net::SocketAddr) {
        if self.is_server {
            if let Some(s) = &mut self.server {
                s.listen_host = addr.ip().to_string();
                s.listen_port = addr.port();
            }
        } else if let Some(c) = &mut self.client {
            c.listen_host = addr.ip().to_string();
            c.listen_port = addr.port();
        }
    }

    pub fn disable_tls(&self) -> bool {
        if self.is_server {
            if let Some(s) = &self.server {
                return s.disable_tls.unwrap_or(false);
            }
        } else if let Some(c) = &self.client {
            return c.disable_tls.unwrap_or(false);
        }
        false
    }

    pub fn dangerous_mode(&self) -> bool {
        if let Some(c) = &self.client {
            return c.dangerous_mode.unwrap_or(false);
        }
        false
    }

    pub fn set_dangerous_mode(&mut self, dangerous_mode: bool) {
        if let Some(c) = &mut self.client {
            c.dangerous_mode = Some(dangerous_mode);
        }
    }

    pub fn cache_dns(&self) -> bool {
        self.client.as_ref().is_some_and(|c| c.cache_dns)
    }

    pub fn set_cache_dns(&mut self, cache_dns: bool) {
        if let Some(c) = &mut self.client {
            c.cache_dns = cache_dns;
        }
    }

    pub fn check_correctness(&mut self, is_server: bool) -> Result<()> {
        self.is_server = is_server;
        if self.is_server {
            if self.server.is_none() {
                return Err("Configuration needs server settings".into());
            }
            self.client = None;
        } else {
            if self.client.is_none() {
                return Err("Configuration needs client settings".into());
            }
            self.server = None;
        }

        if self.tunnel_path.is_empty() {
            self.tunnel_path = TunnelPath::default();
        } else {
            self.tunnel_path.standardize();
        }

        if let Some(server) = &mut self.server {
            if server.listen_host.is_empty() {
                server.listen_host = Ipv4Addr::UNSPECIFIED.to_string();
            }
            if server.listen_port == 0 {
                server.listen_port = 443;
            }
        }
        if let Some(client) = &mut self.client {
            let server_host = client.server_host.clone();
            let server_host = match (server_host.is_empty(), client.server_domain.clone()) {
                (true, Some(domain)) => match domain.is_empty() {
                    true => return Err(Error::from("We need server host in client settings")),
                    false => domain,
                },
                (true, None) => return Err(Error::from("We need server host in client settings")),
                (false, _) => server_host,
            };
            if client.server_host.is_empty() {
                client.server_host.clone_from(&server_host);
            }
            if client.server_domain.is_none() || client.server_domain.as_ref().unwrap_or(&"".to_string()).is_empty() {
                client.server_domain = Some(server_host.clone());
            }

            if client.server_port == 0 {
                client.server_port = 443;
            }

            if !self.is_server {
                let mut addr = (server_host, client.server_port).to_socket_addrs()?;
                let addr = addr.next().ok_or("address not available")?;
                {
                    let timeout = std::time::Duration::from_secs(self.test_timeout_secs.unwrap_or(TEST_TIMEOUT_SECS));
                    crate::tcp_stream::std_create(addr, Some(timeout))?;
                }
                if client.listen_host.is_empty() {
                    client.listen_host = if addr.is_ipv4() {
                        Ipv4Addr::LOCALHOST.to_string()
                    } else {
                        Ipv6Addr::LOCALHOST.to_string()
                    };
                }
                client.server_ip_addr = Some(addr);
            }
        }
        Ok(())
    }

    /// load from overtls config file
    pub fn from_config_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
        let f = std::fs::File::open(&path)?;
        let config: Config = serde_json::from_reader(f)?;
        Ok(config)
    }

    /// load from `ssr://...` style url
    pub fn from_ssr_url(url: &str) -> Result<Self> {
        let engine = base64easy::EngineKind::UrlSafeNoPad;
        let url = url.trim_start_matches("ssr://");
        let url = base64easy::decode(url, engine)?;
        let url = String::from_utf8(url)?;
        // split string by `/?`
        let mut parts = url.split("/?");

        // split first part by `:` and collect to vector
        let mut parts0 = parts.next().ok_or("url is invalid")?.split(':').collect::<Vec<&str>>();
        // check if parts length is less than 6
        if parts0.len() < 6 {
            return Err("url is invalid".into());
        }
        let host = parts0.remove(0);
        let port = parts0.remove(0);
        let protocol = parts0.remove(0);
        let method = parts0.remove(0); // none is default
        let obfs = parts0.remove(0);
        let password = String::from_utf8(base64easy::decode(parts0.remove(0), engine)?)?;

        if method != "none" {
            return Err("method is not none".into());
        }
        if obfs != "plain" {
            return Err("obfs is not plain".into());
        }
        if protocol != "origin" {
            return Err("protocol is not origin".into());
        }
        let port = port.parse::<u16>()?;

        // split second part by `&` and collect to vector
        let parts1 = parts.next().ok_or("url is invalid")?.split('&').collect::<Vec<&str>>();
        // for each element in parts1, split by `=` and collect to a hashmap
        let mut map = std::collections::HashMap::new();
        for part in parts1 {
            let mut kv = part.split('=');
            let k = kv.next().ok_or("url is invalid")?;
            let v = kv.next().ok_or("url is invalid")?;
            map.insert(k, v);
        }

        let ot_enable = map.get("ot_enable").map_or("0".to_string(), |r| r.to_string());
        if ot_enable != "1" {
            return Err("ot_enable is not 1".into());
        }
        let remarks = map.get("remarks").and_then(|r| match base64easy::decode(r, engine) {
            Ok(decoded) => String::from_utf8(decoded).ok(),
            Err(_) => None,
        });
        let ot_domain = map.get("ot_domain").and_then(|r| match base64easy::decode(r, engine) {
            Ok(decoded) => String::from_utf8(decoded).ok(),
            Err(_) => None,
        });
        let ot_path = map.get("ot_path").ok_or("ot_path is not set")?;
        let ot_path = String::from_utf8(base64easy::decode(ot_path, engine)?)?;

        let ot_cert = map
            .get("ot_cert")
            .and_then(|r| base64easy::decode(r, engine).ok())
            .and_then(|decoded| String::from_utf8(decoded).ok())
            .filter(|s| !s.is_empty());

        let dangerous_mode = map.get("dangerous_mode").and_then(|r| r.parse::<bool>().ok());

        let client = Client {
            server_host: host.to_string(),
            server_port: port,
            server_domain: ot_domain,
            cafile: ot_cert,
            dangerous_mode,
            ..Client::default()
        };

        let config = Config {
            password: Some(password),
            method: Some(method.to_string()),
            remarks,
            tunnel_path: TunnelPath::Single(ot_path),
            client: Some(client),
            ..Config::default()
        };

        Ok(config)
    }

    pub fn generate_ssr_url(&self) -> Result<String> {
        let client = self.client.as_ref().ok_or(Error::from("client is not set"))?;
        let engine = base64easy::EngineKind::UrlSafeNoPad;
        let method = self.method.as_ref().map_or("none".to_string(), |m| m.clone());
        let password = self.password.as_ref().map_or("password".to_string(), |p| p.clone());
        let password = base64easy::encode(password.as_bytes(), engine);
        let remarks = self.remarks.as_ref().map_or("remarks".to_string(), |r| r.clone());
        let remarks = base64easy::encode(remarks.as_bytes(), engine);
        let domain = client.server_domain.as_ref().map_or("".to_string(), |d| d.clone());
        let domain = base64easy::encode(domain.as_bytes(), engine);
        let err = "tunnel_path is not set";
        let tunnel_path = base64easy::encode(self.tunnel_path.extract().first().ok_or(err)?.as_bytes(), engine);
        let host = &client.server_host;
        let port = client.server_port;

        let mut url = format!("{host}:{port}:origin:{method}:plain:{password}/?remarks={remarks}&ot_enable=1");
        url.push_str(&format!("&ot_domain={domain}&ot_path={tunnel_path}"));

        let dangerous_mode = client.dangerous_mode.unwrap_or(false);
        if !dangerous_mode && let Some(ref ca) = client.certificate_content() {
            let ca = base64easy::encode(ca.as_bytes(), engine);
            url.push_str(&format!("&ot_cert={ca}"));
        }

        if let Some(dangerous_mode) = client.dangerous_mode {
            url.push_str(&format!("&dangerous_mode={dangerous_mode}"));
        }

        Ok(format!("ssr://{}", base64easy::encode(url.as_bytes(), engine)))
    }
}

pub(crate) fn generate_ssr_url<P>(path: P) -> Result<String>
where
    P: AsRef<std::path::Path>,
{
    let config = Config::from_config_file(path)?;
    if config.certificate_content().is_some() {
        log::warn!("Certificate content exists!");
    }
    config.generate_ssr_url()
}

#[test]
fn test_config() {
    let client = Client {
        server_host: "www.gov.cn".to_string(),
        server_port: 443,
        listen_host: "127.0.0.1".to_string(),
        listen_port: 0,
        // server_domain: Some("www.gov.cn".to_string()),
        dangerous_mode: Some(false),
        ..Client::default()
    };

    let mut config = Config {
        remarks: Some("test".to_string()),
        method: Some("none".to_string()),
        password: Some("password".to_string()),
        tunnel_path: TunnelPath::Single("/tunnel/".to_string()),
        client: Some(client),
        ..Config::default()
    };

    config.check_correctness(false).unwrap();

    let qrcode = config.generate_ssr_url().unwrap();
    println!("{qrcode:?}");

    let config2 = Config::from_ssr_url(&qrcode).unwrap();
    println!("{config2:?}");

    assert_eq!(config, config2);
}
