#[cfg(not(test))]
use crate::adapters::proxy_router::{
    ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
    ProxyRouterRequest,
};
use crate::models::{
    ComponentId, ComponentState, ComponentStatus, Profile, ProfileItemType, Protocol,
    ProxyProtocol, Target,
};
#[cfg(test)]
use crate::proxy_router::{
    ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
    ProxyRouterRequest,
};
use serde::{Deserialize, Serialize};

pub const PROXIFYRE_ADAPTER_ID: &str = "proxifyre";
pub const PROXIFYRE_OUTPUT_FILE: &str = "proxifyre-app-config.json";

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProxiFyreAdapter {
    log_level: String,
    bypass_lan: bool,
}

impl ProxiFyreAdapter {
    pub fn new(log_level: impl Into<String>, bypass_lan: bool) -> Self {
        Self {
            log_level: log_level.into(),
            bypass_lan,
        }
    }

    pub fn generate_proxifyre_config(
        &self,
        request: ProxyRouterRequest<'_>,
    ) -> Result<ProxiFyreConfig, ProxyRouterError> {
        let mut proxies = Vec::new();

        for profile in request.profiles.iter().filter(|profile| profile.enabled) {
            let target = find_target(profile, request.targets)?;
            ensure_target_supported(profile, target, request.components)?;

            let app_names = app_names_for_profile(profile);
            if app_names.is_empty() {
                return Err(ProxyRouterError::new(
                    ProxyRouterErrorKind::EmptyProfileItems,
                    format!(
                        "В профиле '{}' нет приложений для маршрутизации",
                        profile.id
                    ),
                ));
            }

            proxies.push(ProxiFyreProxy {
                app_names,
                socks5_proxy_endpoint: format!("{}:{}", target.host, target.port),
                supported_protocols: protocols_for_profile(profile),
            });
        }

        Ok(ProxiFyreConfig {
            log_level: self.log_level.clone(),
            bypass_lan: self.bypass_lan,
            proxies,
        })
    }
}

impl Default for ProxiFyreAdapter {
    fn default() -> Self {
        Self::new("Info", true)
    }
}

impl ProxyRouterAdapter for ProxiFyreAdapter {
    fn id(&self) -> &'static str {
        PROXIFYRE_ADAPTER_ID
    }

    fn output_file_name(&self) -> &'static str {
        PROXIFYRE_OUTPUT_FILE
    }

    fn generate_config(
        &self,
        request: ProxyRouterRequest<'_>,
    ) -> Result<ProxyRouterGeneratedConfig, ProxyRouterError> {
        let config = self.generate_proxifyre_config(request)?;
        let enabled_profiles = config.proxies.len();
        let routed_apps = config
            .proxies
            .iter()
            .map(|proxy| proxy.app_names.len())
            .sum();
        let contents = serde_json::to_string_pretty(&config).map_err(|error| {
            ProxyRouterError::new(
                ProxyRouterErrorKind::Serialization,
                format!("Не удалось сериализовать конфиг ProxiFyre: {error}"),
            )
        })?;

        Ok(ProxyRouterGeneratedConfig {
            adapter_id: self.id().to_string(),
            output_file_name: self.output_file_name().to_string(),
            contents,
            enabled_profiles,
            routed_apps,
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProxiFyreConfig {
    #[serde(rename = "logLevel")]
    pub log_level: String,
    #[serde(rename = "bypassLan")]
    pub bypass_lan: bool,
    pub proxies: Vec<ProxiFyreProxy>,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProxiFyreProxy {
    #[serde(rename = "appNames")]
    pub app_names: Vec<String>,
    #[serde(rename = "socks5ProxyEndpoint")]
    pub socks5_proxy_endpoint: String,
    #[serde(rename = "supportedProtocols")]
    pub supported_protocols: Vec<String>,
}

fn find_target<'a>(
    profile: &Profile,
    targets: &'a [Target],
) -> Result<&'a Target, ProxyRouterError> {
    targets
        .iter()
        .find(|target| target.id == profile.target_id)
        .ok_or_else(|| {
            ProxyRouterError::new(
                ProxyRouterErrorKind::MissingTarget,
                format!(
                    "Профиль '{}' ссылается на отсутствующую цель '{}'",
                    profile.id, profile.target_id
                ),
            )
        })
}

fn ensure_target_supported(
    profile: &Profile,
    target: &Target,
    components: &[ComponentStatus],
) -> Result<(), ProxyRouterError> {
    if target.protocol != ProxyProtocol::Socks5 {
        return Err(ProxyRouterError::new(
            ProxyRouterErrorKind::UnsupportedTargetProtocol,
            format!(
                "Цель '{}' использует HTTP, но ProxiFyre требует SOCKS5",
                target.id
            ),
        ));
    }

    if let Some(required_component) = &target.requires_component {
        let Some(status) = components
            .iter()
            .find(|component| &component.id == required_component)
        else {
            return Err(ProxyRouterError::new(
                ProxyRouterErrorKind::MissingRequiredComponent,
                format!(
                    "Цель '{}' профиля '{}' требует отсутствующий компонент '{}'",
                    target.id,
                    profile.id,
                    component_id_label(required_component)
                ),
            ));
        };

        if !component_is_running(status) {
            return Err(ProxyRouterError::new(
                ProxyRouterErrorKind::RequiredComponentNotRunning,
                format!(
                    "Цель '{}' профиля '{}' требует запущенный компонент '{}'",
                    target.id,
                    profile.id,
                    component_id_label(required_component)
                ),
            ));
        }
    }

    Ok(())
}

fn component_is_running(status: &ComponentStatus) -> bool {
    status.installed && status.running && status.state == ComponentState::Running
}

fn app_names_for_profile(profile: &Profile) -> Vec<String> {
    let mut names = Vec::new();

    for item in &profile.items {
        let value = item.value.trim();
        if value.is_empty() {
            continue;
        }

        let app_name = match item.item_type {
            ProfileItemType::Process | ProfileItemType::Folder | ProfileItemType::Exe => value,
        };

        if !names.iter().any(|existing| existing == app_name) {
            names.push(app_name.to_string());
        }
    }

    names
}

fn protocols_for_profile(profile: &Profile) -> Vec<String> {
    let mut protocols = Vec::new();

    for protocol in &profile.protocols {
        let value = match protocol {
            Protocol::Tcp => "TCP",
            Protocol::Udp => "UDP",
        };

        if !protocols.iter().any(|existing| existing == value) {
            protocols.push(value.to_string());
        }
    }

    protocols
}

fn component_id_label(component_id: &ComponentId) -> &'static str {
    match component_id {
        ComponentId::ControlApp => "control-app",
        ComponentId::Proxyfier => "proxyfier",
        ComponentId::Singbox => "singbox",
    }
}
