#[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))]
use std::fs::{File, OpenOptions};

use super::{FirewallError, FirewallRule, Policy, SnatBinding};

#[cfg(all(
    test,
    any(target_os = "freebsd", target_os = "macos", target_os = "netbsd")
))]
const DEV_PF: &str = "/dev/null";
#[cfg(all(
    not(test),
    any(target_os = "freebsd", target_os = "macos", target_os = "netbsd")
))]
const DEV_PF: &str = "/dev/pf";

#[allow(dead_code)]
pub(crate) struct FirewallApi {
    pub(crate) ifname: String,
    #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))]
    pub(crate) file: File,
    #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))]
    pub(crate) default_policy: Policy,
    #[cfg(target_os = "linux")]
    pub(crate) socket: mnl::Socket,
}

impl FirewallApi {
    pub(crate) fn new<S>(ifname: S) -> Result<Self, FirewallError>
    where
        S: Into<String>,
    {
        Ok(Self {
            ifname: ifname.into(),
            #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))]
            file: OpenOptions::new().read(true).write(true).open(DEV_PF)?,
            #[cfg(any(target_os = "freebsd", target_os = "macos", target_os = "netbsd"))]
            default_policy: Policy::Deny,
            #[cfg(target_os = "linux")]
            socket: mnl::Socket::new(mnl::Bus::Netfilter).map_err(|err| {
                FirewallError::NetlinkError(format!("Failed to create socket: {err:?}"))
            })?,
        })
    }
}

pub(crate) trait FirewallManagementApi {
    /// Set up the firewall with `default_policy`, `priority`, and cleans up any existing rules.
    fn setup(&mut self, default_policy: Policy, priority: Option<i32>)
    -> Result<(), FirewallError>;

    /// Clean up the firewall rules.
    fn cleanup(&mut self) -> Result<(), FirewallError>;

    /// Add firewall rules.
    fn add_rules(&mut self, rules: &[FirewallRule]) -> Result<(), FirewallError>;

    /// Setup Network Address Translation using POSTROUTING chain rules
    fn setup_nat(
        &mut self,
        masquerade_enabled: bool,
        snat_bindings: &[SnatBinding],
    ) -> Result<(), FirewallError>;
}
