use std::sync::Arc;

use easytier_core::{
    config::peers::HostRoutingPolicy, instance::CoreInstanceHostConfig,
    peers::credential_manager::CredentialStorage,
};
use strum::VariantArray as _;

#[cfg(feature = "management")]
use crate::common::credential_manager::runtime_credential_storage;
#[cfg(test)]
use crate::common::global_ctx::GlobalCtxEvent;
use crate::{
    common::{constants::EASYTIER_VERSION, global_ctx::ArcGlobalCtx},
    tunnel::IpScheme,
};

/// Projects only native Host policy and build capabilities. All TOML-derived
/// Instance configuration is normalized by `easytier-core`.
pub(crate) fn runtime_core_host_config() -> CoreInstanceHostConfig {
    let hostname = gethostname::gethostname().to_string_lossy().to_string();
    CoreInstanceHostConfig {
        hostname_fallback: (!hostname.is_empty()).then_some(hostname),
        host_routing: HostRoutingPolicy {
            local_exit_node_fallback: cfg!(target_env = "ohos"),
        },
        force_exit_node: cfg!(target_env = "ohos"),
        allow_interface_bind: !cfg!(any(
            target_os = "android",
            target_os = "ios",
            all(target_os = "macos", feature = "macos-ne"),
            target_env = "ohos"
        )),
        smoltcp_available: cfg!(feature = "smoltcp"),
        requires_smoltcp: cfg!(any(
            target_os = "android",
            target_os = "ios",
            all(target_os = "macos", feature = "macos-ne"),
            target_env = "ohos"
        )),
        icmp_failure_is_fatal: cfg!(not(any(
            target_os = "android",
            target_os = "ios",
            all(target_os = "macos", feature = "macos-ne"),
            target_env = "ohos"
        ))),
        public_ipv6_provider_supported: cfg!(all(
            target_os = "linux",
            feature = "public-ipv6-provider"
        )),
        gateway_enabled: cfg!(feature = "socks5"),
        proxy_enabled: cfg!(any(feature = "kcp", feature = "quic")),
        vpn_portal_enabled: cfg!(feature = "wireguard"),
        magic_dns_enabled: cfg!(feature = "magic-dns"),
        kcp_enabled: cfg!(feature = "kcp"),
        quic_enabled: cfg!(feature = "quic"),
        udp_broadcast_enabled: cfg!(all(target_os = "windows", feature = "tun")),
        upnp_enabled: cfg!(feature = "upnp"),
        tcp_hole_punching_enabled: cfg!(feature = "tcp-hole-punch"),
        ignore_unsupported_config: false,
        easytier_version: EASYTIER_VERSION.to_owned(),
        endpoint_protocols: IpScheme::VARIANTS.iter().map(ToString::to_string).collect(),
    }
}

pub(crate) fn compact_runtime_core_host_config() -> CoreInstanceHostConfig {
    let mut config = runtime_core_host_config();
    config.force_exit_node = false;
    config.host_routing.local_exit_node_fallback = false;
    config.public_ipv6_provider_supported = false;
    config.gateway_enabled = false;
    config.proxy_enabled = false;
    config.vpn_portal_enabled = false;
    config.magic_dns_enabled = false;
    config.kcp_enabled = false;
    config.quic_enabled = false;
    config.udp_broadcast_enabled = false;
    config.upnp_enabled = false;
    config.tcp_hole_punching_enabled = false;
    config.ignore_unsupported_config = true;
    config.endpoint_protocols = vec!["tcp".to_owned(), "udp".to_owned()];
    config
}

pub(crate) fn runtime_peer_credential_storage(
    global_ctx: &ArcGlobalCtx,
) -> Option<Arc<dyn CredentialStorage>> {
    #[cfg(not(feature = "management"))]
    {
        let _ = global_ctx;
        None
    }
    #[cfg(feature = "management")]
    runtime_credential_storage(global_ctx.config.get_credential_file())
}

#[cfg(test)]
pub(crate) fn test_core_instance_config(
    global_ctx: &ArcGlobalCtx,
) -> easytier_core::instance::CoreInstanceConfig {
    use easytier_core::config::toml::{ConfigLoader as _, TomlConfig};

    let config = TomlConfig::new_from_str(&global_ctx.config.dump())
        .expect("test configuration should round-trip through TOML");
    config.set_ipv4(global_ctx.get_ipv4());
    config.set_ipv6(global_ctx.get_ipv6());
    config.set_flags(global_ctx.get_flags());
    let mut host = runtime_core_host_config();
    let hostname = global_ctx.get_hostname();
    host.hostname_fallback = (!hostname.is_empty()).then_some(hostname);
    easytier_core::instance::CoreInstanceConfig::from_toml_with_host(&config, &host)
        .expect("test configuration should normalize")
}

#[cfg(test)]
pub(crate) fn test_runtime_instance_config(
    global_ctx: &ArcGlobalCtx,
) -> easytier_core::config::runtime::CoreInstanceRuntimeConfig {
    let config = test_core_instance_config(global_ctx);
    easytier_core::config::runtime::CoreInstanceRuntimeConfig {
        services: config.connectivity.runtime,
        peer: Arc::new(config.peer.snapshot),
    }
}

#[cfg(test)]
mod tests {
    use easytier_core::{
        config::toml::TomlConfig,
        events::{CoreEvent, CoreEventSink},
    };

    use crate::common::global_ctx::tests::get_mock_global_ctx;

    use super::*;

    #[test]
    fn native_host_config_contains_only_platform_policy() {
        let config = runtime_core_host_config();
        let hostname = gethostname::gethostname().to_string_lossy().to_string();

        assert_eq!(
            config.hostname_fallback,
            (!hostname.is_empty()).then_some(hostname)
        );
        assert_eq!(config.gateway_enabled, cfg!(feature = "socks5"));
        assert_eq!(config.smoltcp_available, cfg!(feature = "smoltcp"));
        assert_eq!(
            config.public_ipv6_provider_supported,
            cfg!(all(target_os = "linux", feature = "public-ipv6-provider"))
        );
        assert_eq!(config.easytier_version, EASYTIER_VERSION);
    }

    #[test]
    fn clearing_configured_hostname_does_not_reuse_the_old_value() {
        let host = runtime_core_host_config();
        let initial = TomlConfig::new_from_str("hostname = \"configured-host\"").unwrap();
        let cleared = TomlConfig::new_from_str("hostname = \"\"").unwrap();

        let initial =
            easytier_core::instance::CoreInstanceConfig::from_toml_with_host(&initial, &host)
                .unwrap();
        let cleared =
            easytier_core::instance::CoreInstanceConfig::from_toml_with_host(&cleared, &host)
                .unwrap();

        assert_eq!(
            initial.peer.snapshot.runtime.core.node.hostname.as_deref(),
            Some("configured-host")
        );
        assert_ne!(
            cleared.peer.snapshot.runtime.core.node.hostname.as_deref(),
            Some("configured-host")
        );
        assert_eq!(
            cleared.peer.snapshot.runtime.core.node.hostname.as_deref(),
            host.hostname_fallback.as_deref()
        );
    }

    #[test]
    fn test_config_uses_current_global_context_hostname_as_fallback() {
        let global_ctx = get_mock_global_ctx();
        global_ctx.set_hostname("test-hostname".to_owned());

        let config = test_core_instance_config(&global_ctx);

        assert_eq!(
            config.peer.snapshot.runtime.core.node.hostname.as_deref(),
            Some("test-hostname")
        );
    }

    #[tokio::test]
    async fn core_event_sink_projects_peer_events_to_global_context() {
        let global_ctx = get_mock_global_ctx();
        let mut events = global_ctx.subscribe();
        CoreEventSink::emit(global_ctx.as_ref(), CoreEvent::PeerAdded(7));

        assert!(matches!(
            events.recv().await.unwrap(),
            GlobalCtxEvent::PeerAdded(7)
        ));
    }

    #[tokio::test]
    async fn core_event_sink_projects_credential_changes_to_global_context() {
        let global_ctx = get_mock_global_ctx();
        let mut events = global_ctx.subscribe();
        CoreEventSink::emit(global_ctx.as_ref(), CoreEvent::CredentialChanged);

        assert!(matches!(
            events.recv().await.unwrap(),
            GlobalCtxEvent::CredentialChanged
        ));
    }
}
