use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr};
use std::path::{Path, PathBuf};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Config {
    pub server: ServerConfig,
    pub network: NetworkConfig,
    pub paths: Paths,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct ServerConfig {
    /// Address the web/API server binds to.
    pub http_bind: IpAddr,
    /// Port for the web/API + ISO/iPXE HTTP server (single port, multiplexed by path).
    pub http_port: u16,
    /// Address the TFTP server binds to.
    pub tftp_bind: IpAddr,
    /// Port for TFTP (RFC 1350 default is 69).
    pub tftp_port: u16,
    /// External hostname/IP clients should use to reach this server. If
    /// `None`, auto-detect from the interface that received the DHCP request
    /// (via IP_PKTINFO). This is what ends up in DHCP option 54 / siaddr,
    /// option 66 (TFTP server), and the base of generated iPXE URLs.
    pub public_ip: Option<Ipv4Addr>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct NetworkConfig {
    pub dhcp_mode: DhcpMode,
    /// Address the DHCP proxy/server binds to. For proxy mode, usually 0.0.0.0.
    pub dhcp_bind: IpAddr,
    /// UDP port for DHCP server-side receive. Standard is 67.
    pub dhcp_port: u16,
    /// UDP port for PXE Boot Server discovery. Standard is 4011.
    pub pxe_port: u16,
    /// Optional allowlist of client MAC prefixes (OUI). Empty = serve everyone.
    pub mac_allowlist: Vec<String>,
    /// Optional allowlist of subnets (CIDR). Empty = serve everyone.
    pub subnet_allowlist: Vec<String>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum DhcpMode {
    /// Run as DHCP proxy (RFC 4578): reply with boot info only, don't lease
    /// IPs. Coexists with an existing DHCP server on the network. Default
    /// because it's the only mode that works in most real deployments without
    /// taking over address assignment.
    #[default]
    Proxy,
    /// Disabled — rely on an external DHCP server that has been manually
    /// configured with `next-server` / `filename`. PXEForge only serves TFTP
    /// + HTTP in this mode. Useful for home routers that can be pre-set.
    Disabled,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct Paths {
    /// Directory holding uploaded ISO files.
    pub iso_dir: PathBuf,
    /// Directory for extracted kernel/initrd and other per-ISO derived assets.
    pub work_dir: PathBuf,
    /// Directory containing bundled iPXE binaries (undionly.kpxe, snponly.efi, ...).
    pub ipxe_dir: PathBuf,
    /// Path to the wimboot binary for Windows ISOs (optional — feature-gated).
    pub wimboot_path: Option<PathBuf>,
    /// Directory under which Windows ISOs are extracted and served via SMB.
    /// Only used when `settings.windows_enabled = true`. Defaults to
    /// `/var/lib/pxeforge/smb` in the container image.
    pub smb_dir: PathBuf,
}

impl Default for ServerConfig {
    fn default() -> Self {
        Self {
            http_bind: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
            http_port: 80,
            tftp_bind: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
            tftp_port: 69,
            public_ip: None,
        }
    }
}

impl Default for NetworkConfig {
    fn default() -> Self {
        Self {
            dhcp_mode: DhcpMode::Proxy,
            dhcp_bind: IpAddr::V4(Ipv4Addr::UNSPECIFIED),
            dhcp_port: 67,
            pxe_port: 4011,
            mac_allowlist: Vec::new(),
            subnet_allowlist: Vec::new(),
        }
    }
}

impl Default for Paths {
    fn default() -> Self {
        Self {
            iso_dir: PathBuf::from("/var/lib/pxeforge/isos"),
            work_dir: PathBuf::from("/var/lib/pxeforge/work"),
            ipxe_dir: PathBuf::from("/usr/share/pxeforge/ipxe"),
            wimboot_path: None,
            smb_dir: PathBuf::from("/var/lib/pxeforge/smb"),
        }
    }
}

impl Default for Config {
    fn default() -> Self {
        Self {
            server: ServerConfig::default(),
            network: NetworkConfig::default(),
            paths: Paths::default(),
        }
    }
}

impl Config {
    pub fn from_toml_file(path: &Path) -> crate::Result<Self> {
        let text = std::fs::read_to_string(path)?;
        toml::from_str(&text).map_err(|e| crate::Error::Config(e.to_string()))
    }

    /// Apply environment variable overrides. Env var names follow the pattern
    /// `PXEFORGE_<SECTION>_<FIELD>`, uppercase. Unknown vars are ignored.
    /// Call this after loading the TOML file so env takes precedence.
    pub fn apply_env(&mut self) {
        if let Ok(v) = std::env::var("PXEFORGE_HTTP_PORT") {
            if let Ok(p) = v.parse() { self.server.http_port = p; }
        }
        if let Ok(v) = std::env::var("PXEFORGE_TFTP_PORT") {
            if let Ok(p) = v.parse() { self.server.tftp_port = p; }
        }
        if let Ok(v) = std::env::var("PXEFORGE_DHCP_PORT") {
            if let Ok(p) = v.parse() { self.network.dhcp_port = p; }
        }
        if let Ok(v) = std::env::var("PXEFORGE_PUBLIC_IP") {
            if let Ok(ip) = v.parse() { self.server.public_ip = Some(ip); }
        }
        if let Ok(v) = std::env::var("PXEFORGE_DHCP_MODE") {
            self.network.dhcp_mode = match v.to_ascii_lowercase().as_str() {
                "proxy" => DhcpMode::Proxy,
                "disabled" | "off" | "none" => DhcpMode::Disabled,
                _ => self.network.dhcp_mode,
            };
        }
        if let Ok(v) = std::env::var("PXEFORGE_ISO_DIR") {
            self.paths.iso_dir = PathBuf::from(v);
        }
        if let Ok(v) = std::env::var("PXEFORGE_WORK_DIR") {
            self.paths.work_dir = PathBuf::from(v);
        }
        if let Ok(v) = std::env::var("PXEFORGE_IPXE_DIR") {
            self.paths.ipxe_dir = PathBuf::from(v);
        }
        if let Ok(v) = std::env::var("PXEFORGE_SMB_DIR") {
            self.paths.smb_dir = PathBuf::from(v);
        }
    }
}
