use std::sync::Arc;

use crate::core::client::ClientManager;
use crate::core::config::Settings;
use crate::services::{
    get_service_pid, is_systemd_service_running, run_sudo_command, write_with_sudo_tee,
    ServiceStatus,
};
use log::info;
use sqlx::SqlitePool;
use std::path::PathBuf;

pub struct DhcpService {
    pub(crate) settings: Arc<Settings>,
    pub(crate) db_pool: SqlitePool,
}

impl DhcpService {
    pub fn new(settings: Settings, db_pool: SqlitePool) -> Self {
        Self { settings: Arc::new(settings), db_pool }
    }

    pub async fn generate_config(&self) -> anyhow::Result<()> {
        let config = &self.settings.dhcp;

        let dhcp_config = format!(
            r#"# Global Config
option space ipxe;
option ipxe-encap-opts code 175 = encapsulate ipxe;
option ipxe.priority code 1 = signed integer 8;
option ipxe.keep-san code 8 = unsigned integer 8;
option ipxe.skip-san-boot code 9 = unsigned integer 8;
option ipxe.syslogs code 85 = string;
option ipxe.cert code 91 = string;
option ipxe.privkey code 92 = string;
option ipxe.crosscert code 93 = string;
option ipxe.no-pxedhcp code 176 = unsigned integer 8;
option ipxe.bus-id code 177 = string;
option ipxe.san-filename code 188 = string;
option ipxe.bios-drive code 189 = unsigned integer 8;
option ipxe.username code 190 = string;
option ipxe.password code 191 = string;
option ipxe.reverse-username code 192 = string;
option ipxe.reverse-password code 193 = string;
option ipxe.version code 235 = string;
option iscsi-initiator-iqn code 203 = string;
# Feature indicators
option ipxe.pxeext code 16 = unsigned integer 8;
option ipxe.iscsi code 17 = unsigned integer 8;
option ipxe.aoe code 18 = unsigned integer 8;
option ipxe.http code 19 = unsigned integer 8;
option ipxe.https code 20 = unsigned integer 8;
option ipxe.tftp code 21 = unsigned integer 8;
option ipxe.ftp code 22 = unsigned integer 8;
option ipxe.dns code 23 = unsigned integer 8;
option ipxe.bzimage code 24 = unsigned integer 8;
option ipxe.multiboot code 25 = unsigned integer 8;
option ipxe.slam code 26 = unsigned integer 8;
option ipxe.srp code 27 = unsigned integer 8;
option ipxe.nbi code 32 = unsigned integer 8;
option ipxe.pxe code 33 = unsigned integer 8;
option ipxe.elf code 34 = unsigned integer 8;
option ipxe.comboot code 35 = unsigned integer 8;
option ipxe.efi code 36 = unsigned integer 8;
option ipxe.fcoe code 37 = unsigned integer 8;
option ipxe.vlan code 38 = unsigned integer 8;
option ipxe.menu code 39 = unsigned integer 8;
option ipxe.sdi code 40 = unsigned integer 8;
option ipxe.nfs code 41 = unsigned integer 8;
option client-architecture code 93 = unsigned integer 16;
option ipxe.no-pxedhcp 1;

# DHCP Server Configuration
default-lease-time 86400;
max-lease-time 86400;
authoritative;
allow booting;
allow bootp;
one-lease-per-client true;

# Define a class for PXE clients
class "pxeclients" {{
  match if substring(option vendor-class-identifier, 0, 9) = "PXEClient";
}}

#on commit {{
#  set clip = binary-to-ascii(10, 8, ".", leased-address);
#  set clmac = concat(
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 1, 1))), 2), ":",
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 2, 1))), 2), ":",
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 3, 1))), 2), ":",
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 4, 1))), 2), ":",
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 5, 1))), 2), ":",
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 6, 1))), 2)
#  );
#  execute("/usr/bin/diskless-manager", "auto-add-client", "--mac", clmac, "--ip", clip);
#}}

# DHCP Configuration
subnet {} netmask {} {{
    # Only hand out dynamic leases to PXE clients
    pool {{
        allow members of "pxeclients";
        range {} {};
    }}
    option routers {};
    option domain-name-servers {},{};
    option broadcast-address {};

    # PXE Boot Configuration
    next-server {};
    if exists user-class and option user-class = "iPXE" {{
        filename "http://{}/{}";
    }} elsif option client-architecture = 00:00 {{
        filename "{}";
    }} elsif option client-architecture = 00:06 {{
        filename "{}";
    }} elsif option client-architecture = 00:07 {{
        filename "{}";
    }}
}}

# Static leases will be added here
include "/etc/dhcp/clients.conf";
"#,
            config.subnet_ip,
            config.subnet_mask,
            config.start_ip,
            config.end_ip,
            config.gateway_ip,
            config.dns_server1,
            config.dns_server2,
            config.broadcast_ip,
            config.next_server_ip,
            config.boot_server_ip,
            config.boot_script,
            config.boot_file_legacy,
            config.boot_file_uefi32,
            config.boot_file_uefi64
        );

        // Write configuration to default DHCP config path
        let dhcp_path = PathBuf::from("/etc/dhcp/dhcpd.conf");
        if let Some(parent) = dhcp_path.parent() {
            // Create parent directory with proper permissions using std::fs
            // The actual file writing will use sudo
            std::fs::create_dir_all(parent)
                .map_err(|e| anyhow::anyhow!("Failed to create directory: {}", e))?;
        }

        write_with_sudo_tee("/etc/dhcp/dhcpd.conf", &dhcp_config).await?;

        // Update /etc/default/isc-dhcp-server with selected interfaces
        let interfaces = self.settings.server.interface.join(" ");
        let default_config = format!(
            r#"# Generated by diskless-manager
INTERFACESv4="{}"
INTERFACESv6=""
"#,
            interfaces
        );
        write_with_sudo_tee("/etc/default/isc-dhcp-server", &default_config).await?;

        info!("DHCP configuration and /etc/default/isc-dhcp-server written");

        Ok(())
    }

    pub async fn start(&self) -> anyhow::Result<()> {
        run_sudo_command(["systemctl", "start", "isc-dhcp-server"]).await?;
        info!("DHCP service started");
        Ok(())
    }

    pub async fn stop(&self) -> anyhow::Result<()> {
        run_sudo_command(["systemctl", "stop", "isc-dhcp-server"]).await?;
        info!("DHCP service stopped");
        Ok(())
    }

    pub async fn reload(&self) -> anyhow::Result<()> {
        run_sudo_command(["systemctl", "restart", "isc-dhcp-server"]).await?;
        info!("DHCP service reloaded");
        Ok(())
    }

    pub async fn get_config(&self) -> anyhow::Result<String> {
        let config_path = PathBuf::from("/etc/dhcp/dhcpd.conf");
        if config_path.exists() {
            let content = std::fs::read_to_string(&config_path)?;
            Ok(content)
        } else {
            // Return default configuration if file doesn't exist
            let config = &self.settings.dhcp;

            let dhcp_config = format!(
                r#"# Global Config
option space ipxe;
option ipxe-encap-opts code 175 = encapsulate ipxe;
option ipxe.priority code 1 = signed integer 8;
option ipxe.keep-san code 8 = unsigned integer 8;
option ipxe.skip-san-boot code 9 = unsigned integer 8;
option ipxe.syslogs code 85 = string;
option ipxe.cert code 91 = string;
option ipxe.privkey code 92 = string;
option ipxe.crosscert code 93 = string;
option ipxe.no-pxedhcp code 176 = unsigned integer 8;
option ipxe.bus-id code 177 = string;
option ipxe.san-filename code 188 = string;
option ipxe.bios-drive code 189 = unsigned integer 8;
option ipxe.username code 190 = string;
option ipxe.password code 191 = string;
option ipxe.reverse-username code 192 = string;
option ipxe.reverse-password code 193 = string;
option ipxe.version code 235 = string;
option iscsi-initiator-iqn code 203 = string;
# Feature indicators
option ipxe.pxeext code 16 = unsigned integer 8;
option ipxe.iscsi code 17 = unsigned integer 8;
option ipxe.aoe code 18 = unsigned integer 8;
option ipxe.http code 19 = unsigned integer 8;
option ipxe.https code 20 = unsigned integer 8;
option ipxe.tftp code 21 = unsigned integer 8;
option ipxe.ftp code 22 = unsigned integer 8;
option ipxe.dns code 23 = unsigned integer 8;
option ipxe.bzimage code 24 = unsigned integer 8;
option ipxe.multiboot code 25 = unsigned integer 8;
option ipxe.slam code 26 = unsigned integer 8;
option ipxe.srp code 27 = unsigned integer 8;
option ipxe.nbi code 32 = unsigned integer 8;
option ipxe.pxe code 33 = unsigned integer 8;
option ipxe.elf code 34 = unsigned integer 8;
option ipxe.comboot code 35 = unsigned integer 8;
option ipxe.efi code 36 = unsigned integer 8;
option ipxe.fcoe code 37 = unsigned integer 8;
option ipxe.vlan code 38 = unsigned integer 8;
option ipxe.menu code 39 = unsigned integer 8;
option ipxe.sdi code 40 = unsigned integer 8;
option ipxe.nfs code 41 = unsigned integer 8;
option client-architecture code 93 = unsigned integer 16;
option ipxe.no-pxedhcp 1;

# DHCP Server Configuration
default-lease-time 86400;
max-lease-time 86400;
authoritative;
allow booting;
allow bootp;
one-lease-per-client true;

# Define a class for PXE clients
class "pxeclients" {{
  match if substring(option vendor-class-identifier, 0, 9) = "PXEClient";
}}

#on commit {{
#  set clip = binary-to-ascii(10, 8, ".", leased-address);
#  set clmac = concat(
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 1, 1))), 2), ":",
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 2, 1))), 2), ":",
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 3, 1))), 2), ":",
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 4, 1))), 2), ":",
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 5, 1))), 2), ":",
#    suffix(concat("0", binary-to-ascii(16, 8, "", substring(hardware, 6, 1))), 2)
#  );
#  execute("/usr/bin/diskless-manager", "auto-add-client", "--mac", clmac, "--ip", clip);
#}}

# DHCP Configuration
subnet {} netmask {} {{
    # Only hand out dynamic leases to PXE clients
    pool {{
        allow members of "pxeclients";
        range {} {};
    }}
    option routers {};
    option domain-name-servers {},{};
    option broadcast-address {};

    # PXE Boot Configuration
    next-server {};
    if exists user-class and option user-class = "iPXE" {{
        filename "http://{}/{}";
    }} elsif option client-architecture = 00:00 {{
        filename "{}";
    }} elsif option client-architecture = 00:06 {{
        filename "{}";
    }} elsif option client-architecture = 00:07 {{
        filename "{}";
    }}
}}

# Static leases will be added here
include "/etc/dhcp/clients.conf";
"#,
                config.subnet_ip,
                config.subnet_mask,
                config.start_ip,
                config.end_ip,
                config.gateway_ip,
                config.dns_server1,
                config.dns_server2,
                config.broadcast_ip,
                config.next_server_ip,
                config.boot_server_ip,
                config.boot_script,
                config.boot_file_legacy,
                config.boot_file_uefi32,
                config.boot_file_uefi64
            );
            Ok(dhcp_config)
        }
    }

    pub async fn status(&self) -> anyhow::Result<ServiceStatus> {
        let running = is_systemd_service_running("isc-dhcp-server").await?;
        let pid = get_service_pid("isc-dhcp-server").await?;
        Ok(ServiceStatus {
            running,
            pid,
            message: if running {
                "DHCP server is running".to_string()
            } else {
                "DHCP server is not running".to_string()
            },
        })
    }

    pub async fn generate_client_configs(&self) -> anyhow::Result<()> {
        run_sudo_command(["rm", "/var/lib/dhcp/dhcpd.leases"]).await?;
        info!("DHCP leases cleared");

        // Add static host entries for registered clients
        let client_manager = ClientManager::new(self.db_pool.clone());
        let clients = client_manager.list().await?;

        let mut client_config = String::new();

        for client in clients {
            if !client.ip.is_empty() && client.ip != "N/A" {
                // Only add root-path if target_iqn is available
                if let Some(ref target_iqn) = client.target_iqn {
                    let server_ip = crate::cmd::get_server_ip(); // Get server IP for iSCSI
                    client_config.push_str(&format!(
                        r#"
host {name} {{
    hardware ethernet {mac};
    fixed-address {ip};
    option host-name "{name}";
    option root-path "iscsi:{server_ip}::::{target_iqn}";
}}
"#,
                        name = client.name,
                        mac = client.mac,
                        ip = client.ip,
                        server_ip = server_ip,
                        target_iqn = target_iqn,
                    ));
                } else {
                    // Fallback to basic host entry if no target_iqn
                    client_config.push_str(&format!(
                        r#"
host {name} {{
    hardware ethernet {mac};
    fixed-address {ip};
    option host-name "{name}";
}}
"#,
                        name = client.name,
                        mac = client.mac,
                        ip = client.ip,
                    ));
                }
            }
        }

        // Write configuration to default DHCP config path
        let config_path = PathBuf::from("/etc/dhcp/clients.conf");
        if let Some(parent) = config_path.parent() {
            // Create parent directory with proper permissions using std::fs
            // The actual file writing will use sudo
            std::fs::create_dir_all(parent)
                .map_err(|e| anyhow::anyhow!("Failed to create directory: {}", e))?;
        }

        write_with_sudo_tee("/etc/dhcp/clients.conf", &client_config).await?;

        info!("Client configuration written to {}", config_path.display());
        Ok(())
    }
}

#[expect(dead_code, reason = "Utility function kept for potential future DHCP network calculations")]
fn calculate_network(ip: &str, netmask: &str) -> anyhow::Result<String> {
    let ip_parts: Vec<u8> = ip
        .split('.')
        .map(|s| s.parse())
        .collect::<Result<Vec<_>, _>>()?;
    let mask_parts: Vec<u8> = netmask
        .split('.')
        .map(|s| s.parse())
        .collect::<Result<Vec<_>, _>>()?;

    let network: Vec<String> = ip_parts
        .iter()
        .zip(mask_parts.iter())
        .map(|(ip, mask)| (ip & mask).to_string())
        .collect();

    Ok(network.join("."))
}
