//! `TunnelKind` aggregate — runtime-selectable tunnel dispatcher (plan 004 U4).
//!
//! The engine routes `profile.protocol → TunnelKind` exactly once via
//! [`tunnel_for`]; everything downstream calls the trait without protocol
//! match arms.
//!
//! The aggregate lives in the binary (not `vortix-core`) for the same
//! Cargo-cycle reason as `Platform` (plan 003): the protocol crates already
//! depend on `vortix-core`.

use std::path::Path;

use crate::vortix_core::ports::tunnel::{
    ParseError, ParsedProfile, Tunnel, TunnelCapabilities, TunnelError, TunnelHandle,
    TunnelKindTag, TunnelStatus,
};
use crate::vortix_core::profile::{Profile, ProfileId, ProtocolKind};
use crate::vortix_protocol_openvpn::OvpnTunnel;
use crate::vortix_protocol_wireguard::WgTunnel;

use crate::state::{Protocol, VpnProfile};

/// Runtime-selectable carrier over the closed protocol set.
///
/// Mock variant uses `crate::vortix_core::ports::tunnel::mock::MockTunnel` so tests
/// can substitute scripted behaviour without touching the real impls.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum TunnelKind {
    WireGuard(WgTunnel),
    OpenVpn(OvpnTunnel),
    Mock(crate::vortix_core::ports::tunnel::mock::MockTunnel),
}

impl TunnelKind {
    pub fn up(&mut self, profile: &Profile) -> Result<TunnelHandle, TunnelError> {
        match self {
            Self::WireGuard(t) => t.up(profile),
            Self::OpenVpn(t) => t.up(profile),
            Self::Mock(t) => t.up(profile),
        }
    }

    pub fn down(&mut self, handle: TunnelHandle) -> Result<(), TunnelError> {
        match self {
            Self::WireGuard(t) => t.down(handle),
            Self::OpenVpn(t) => t.down(handle),
            Self::Mock(t) => t.down(handle),
        }
    }

    pub fn status(&self, handle: &TunnelHandle) -> Result<TunnelStatus, TunnelError> {
        match self {
            Self::WireGuard(t) => t.status(handle),
            Self::OpenVpn(t) => t.status(handle),
            Self::Mock(t) => t.status(handle),
        }
    }

    pub fn parse_profile(&self, raw: &[u8]) -> Result<Box<dyn ParsedProfile>, ParseError> {
        match self {
            Self::WireGuard(t) => t.parse_profile(raw),
            Self::OpenVpn(t) => t.parse_profile(raw),
            Self::Mock(t) => t.parse_profile(raw),
        }
    }

    #[must_use]
    pub fn capabilities(&self) -> TunnelCapabilities {
        match self {
            Self::WireGuard(t) => t.capabilities(),
            Self::OpenVpn(t) => t.capabilities(),
            Self::Mock(t) => t.capabilities(),
        }
    }

    #[must_use]
    pub fn kind_tag(&self) -> TunnelKindTag {
        match self {
            Self::WireGuard(t) => t.kind_tag(),
            Self::OpenVpn(t) => t.kind_tag(),
            Self::Mock(t) => t.kind_tag(),
        }
    }
}

// Implement the `Tunnel` trait by delegating to the inherent methods.
// Plan 005's `Engine<T: Tunnel>` requires this so the binary can construct
// `Engine<TunnelKind>` and drive the FSM with the existing dispatch.
impl crate::vortix_core::ports::tunnel::Tunnel for TunnelKind {
    fn up(&mut self, profile: &Profile) -> Result<TunnelHandle, TunnelError> {
        TunnelKind::up(self, profile)
    }
    fn down(&mut self, handle: TunnelHandle) -> Result<(), TunnelError> {
        TunnelKind::down(self, handle)
    }
    fn status(&self, handle: &TunnelHandle) -> Result<TunnelStatus, TunnelError> {
        TunnelKind::status(self, handle)
    }
    fn parse_profile(&self, raw: &[u8]) -> Result<Box<dyn ParsedProfile>, ParseError> {
        TunnelKind::parse_profile(self, raw)
    }
    fn capabilities(&self) -> TunnelCapabilities {
        TunnelKind::capabilities(self)
    }
    fn kind_tag(&self) -> TunnelKindTag {
        TunnelKind::kind_tag(self)
    }
}

/// THE single routing function: protocol → `TunnelKind`.
///
/// Engine and CLI call this once per connect/disconnect and never branch on
/// protocol again. Adding a third protocol means adding one variant here.
#[must_use]
pub fn tunnel_for(
    protocol: Protocol,
    config_dir: &Path,
    ovpn_verbosity: &str,
    connect_timeout_secs: u64,
) -> TunnelKind {
    match protocol {
        Protocol::WireGuard => TunnelKind::WireGuard(WgTunnel::new()),
        Protocol::OpenVPN => TunnelKind::OpenVpn(
            OvpnTunnel::new(config_dir.join(crate::constants::OPENVPN_RUN_DIR))
                .with_auth_dir(config_dir.join(crate::constants::OPENVPN_AUTH_DIR))
                .with_verbosity(ovpn_verbosity)
                .with_connect_timeout(connect_timeout_secs),
        ),
    }
}

/// Build a `vortix-core` [`Profile`] view from the binary-side `VpnProfile`.
///
/// Plan 007 reconciles the two profile types; until then the engine
/// translates at the trait boundary.
#[must_use]
pub fn profile_view(p: &VpnProfile) -> Profile {
    Profile::new(
        ProfileId::new(&p.name),
        &p.name,
        match p.protocol {
            Protocol::WireGuard => ProtocolKind::WireGuard,
            Protocol::OpenVPN => ProtocolKind::OpenVpn,
        },
        p.config_path.clone(),
    )
}
