#[cfg(target_os = "android")]
use crate::tun_provider::imp::VpnServiceConfig;
use cfg_if::cfg_if;
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

cfg_if! {
    if #[cfg(target_os = "android")] {
        #[path = "android/mod.rs"]
        mod imp;
        use self::imp::{AndroidTunProvider, VpnServiceTun};
        pub use self::imp::Error;

        pub type Tun = VpnServiceTun;
        pub type TunProvider = AndroidTunProvider;
    } else if #[cfg(all(unix, not(target_os = "android")))] {
        #[path = "unix.rs"]
        mod imp;
        use self::imp::{UnixTun, UnixTunProvider};
        pub use self::imp::Error;

        pub type Tun = UnixTun;
        pub type TunProvider = UnixTunProvider;
    } else if #[cfg(windows)] {
        #[path = "windows.rs"]
        mod imp;
        use self::imp::{WindowsTun, WindowsTunProvider};
        pub use self::imp::Error;

        pub type Tun = WindowsTun;
        pub type TunProvider = WindowsTunProvider;
    } else {
        mod stub;
        use self::stub::StubTunProvider;
        pub use self::stub::Error;

        pub type TunProvider = StubTunProvider;
    }
}

/// Configuration for creating a tunnel device.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TunConfig {
    /// Interface name to use.
    #[cfg(target_os = "linux")]
    pub name: Option<String>,

    /// Whether to enable the packet_information option on the tun device.
    #[cfg(target_os = "linux")]
    pub packet_information: bool,

    /// IP addresses for the tunnel interface.
    pub addresses: Vec<IpAddr>,

    /// MTU of the tunnel interface.
    pub mtu: u16,

    /// IPv4 address of the VPN server, and the default IPv4 DNS resolver.
    pub ipv4_gateway: Ipv4Addr,

    /// IPv6 address of the VPN server, and the default IPv6 DNS resolver.
    pub ipv6_gateway: Option<Ipv6Addr>,

    /// Routes to configure for the tunnel.
    pub routes: Vec<IpNetwork>,

    /// Exclude private IPs from the tunnel
    pub allow_lan: bool,

    /// DNS servers to use for the tunnel config.
    /// Unless specified, the gateways will be used for DNS
    pub dns_servers: Option<Vec<IpAddr>>,

    /// Applications to exclude from the tunnel.
    pub excluded_packages: Vec<String>,

    /// Path to resource directory
    #[cfg(target_os = "windows")]
    pub resource_dir: std::path::PathBuf,
}

impl TunConfig {
    /// Return a copy of all gateway addresses
    pub fn gateways(&self) -> Vec<IpAddr> {
        let mut servers = vec![self.ipv4_gateway.into()];
        if let Some(gateway) = self.ipv6_gateway {
            servers.push(gateway.into());
        }
        servers
    }

    /// Routes to configure for the tunnel.
    #[cfg(target_os = "android")]
    pub fn real_routes(&self) -> Vec<IpNetwork> {
        VpnServiceConfig::new(self.clone())
            .routes
            .iter()
            .map(IpNetwork::from)
            .collect()
    }

    /// Applies the blocking config to the current tunnel config
    ///
    /// This is so that if the android system rejects the tun config due to some improper values
    /// we will apply a set of safe values that work so that we can properly route all
    /// traffic in the tunnel and block it.
    #[cfg(target_os = "android")]
    pub fn set_blocking_config(&mut self) {
        let blocking_config = blocking_config();
        self.mtu = blocking_config.mtu;
        self.addresses = blocking_config.addresses;
        self.ipv4_gateway = blocking_config.ipv4_gateway;
        self.ipv6_gateway = blocking_config.ipv6_gateway;
        self.routes = blocking_config.routes;
        self.dns_servers = blocking_config.dns_servers;
    }
}

/// Return a tunnel configuration that routes all traffic inside the tunnel.
///
/// Most values except the routes are nonsensical. This is mostly used as a reasonable default on
/// Android to route all traffic inside the tunnel.
pub fn blocking_config(
    #[cfg(target_os = "windows")] resource_dir: std::path::PathBuf,
) -> TunConfig {
    TunConfig {
        #[cfg(target_os = "linux")]
        name: None,
        #[cfg(target_os = "linux")]
        packet_information: false,
        addresses: vec![
            IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)),
            IpAddr::V6(Ipv6Addr::new(0xfd00, 0, 0, 0, 0, 0, 0, 1)),
        ],
        mtu: 1380,
        ipv4_gateway: Ipv4Addr::new(10, 64, 0, 1),
        ipv6_gateway: None,
        routes: DEFAULT_ROUTES.to_vec(),
        allow_lan: false,
        #[cfg(target_os = "android")]
        dns_servers: Some(vec![IpAddr::V4(Ipv4Addr::new(192, 0, 2, 1))]),
        #[cfg(not(target_os = "android"))]
        dns_servers: None,
        excluded_packages: vec![],
        #[cfg(target_os = "windows")]
        resource_dir,
    }
}

const DEFAULT_ROUTES: [IpNetwork; 2] = [
    IpNetwork::V4(IPV4_DEFAULT_ROUTE),
    IpNetwork::V6(IPV6_DEFAULT_ROUTE),
];
const IPV4_DEFAULT_ROUTE: Ipv4Network = Ipv4Network::new_checked(Ipv4Addr::UNSPECIFIED, 0).unwrap();
const IPV6_DEFAULT_ROUTE: Ipv6Network = Ipv6Network::new_checked(Ipv6Addr::UNSPECIFIED, 0).unwrap();
