use core::time::Duration;
use std::{ptr::NonNull, sync::Mutex};

#[cfg(feature = "pci")]
use ax_driver::PciIrqRequirement;
#[cfg(feature = "pci")]
use ax_driver::binding_info_from_pci;
use ax_driver::{
    BindingIrq, BindingIrqSource, binding_info_from_acpi_route, binding_info_from_fdt,
    binding_irq_from_named_fdt_interrupt,
};
use axklib::{
    AxError, AxResult, BoxedIrqHandler, ConcurrentBoxedIrqHandler, IrqCpuMask, IrqHandle, IrqId,
    Klib, PhysAddr, VirtAddr, impl_trait,
};
use fdt_edit::{Fdt, Node, Phandle, Property};
#[cfg(feature = "pci")]
use rdrive::probe::pci::{PciAddress, PciInfo};
use rdrive::{
    DriverGeneric, Platform,
    probe::{
        OnProbeError,
        acpi::{AcpiGsiController, AcpiGsiRoute, AcpiIrqPolarity, AcpiIrqTrigger},
    },
    register::{DriverRegister, ProbeFdt, ProbeKind, ProbeLevel, ProbePriority},
};

static CAPTURED_IRQ: Mutex<Option<Option<BindingIrq>>> = Mutex::new(None);
static SETUP_SPECIFIER: Mutex<Option<Vec<u32>>> = Mutex::new(None);
static SETUP_ACPI_ROUTE: Mutex<Option<AcpiGsiRoute>> = Mutex::new(None);
static RDRIVE_TEST_LOCK: Mutex<()> = Mutex::new(());

const TEST_INTC_DOMAIN: irq_framework::IrqDomainId = irq_framework::IrqDomainId(0);

static TEST_INTC_PROBE_KINDS: &[ProbeKind] = &[ProbeKind::Fdt {
    compatibles: &["test,intc"],
    on_probe: register_test_intc,
}];

static TEST_DEVICE_PROBE_KINDS: &[ProbeKind] = &[ProbeKind::Fdt {
    compatibles: &["test,binding-info"],
    on_probe: capture_binding_info,
}];

struct KlibImpl;

impl_trait! {
    impl Klib for KlibImpl {
        fn mem_iomap(_addr: PhysAddr, _size: usize) -> AxResult<VirtAddr> {
            Err(AxError::Unsupported)
        }

        fn mem_virt_to_phys(addr: VirtAddr) -> PhysAddr {
            PhysAddr::from_usize(addr.as_usize())
        }

        fn mem_make_dma_coherent_uncached(_addr: VirtAddr, _size: usize) -> AxResult {
            Err(AxError::Unsupported)
        }

        fn mem_restore_dma_cached(_addr: VirtAddr, _size: usize) -> AxResult {
            Err(AxError::Unsupported)
        }

        fn dma_alloc_pages(_dma_mask: u64, _num_pages: usize, _align: usize) -> AxResult<VirtAddr> {
            Err(AxError::Unsupported)
        }

        fn dma_dealloc_pages(_addr: VirtAddr, _num_pages: usize) {}

        fn time_busy_wait(_dur: Duration) {}

        fn time_monotonic_nanos() -> u64 {
            0
        }

        fn time_try_init_epoch_offset(_epoch_time_nanos: u64) -> bool {
            false
        }

        fn irq_set_enable(_irq: IrqId, _enabled: bool) -> axklib::AxResult {
            Ok(())
        }

        fn irq_request_shared(
            _irq: IrqId,
            _handler: BoxedIrqHandler,
        ) -> AxResult<IrqHandle> {
            Err(AxError::Unsupported)
        }

        fn irq_request_shared_disabled(
            _irq: IrqId,
            _handler: BoxedIrqHandler,
        ) -> AxResult<IrqHandle> {
            Err(AxError::Unsupported)
        }

        fn irq_request_percpu(
            _irq: IrqId,
            _cpus: IrqCpuMask,
            _handler: ConcurrentBoxedIrqHandler,
        ) -> AxResult<IrqHandle> {
            Err(AxError::Unsupported)
        }

        fn irq_free(_handle: IrqHandle) -> AxResult {
            Err(AxError::Unsupported)
        }

        fn irq_enable(_handle: IrqHandle) -> AxResult {
            Err(AxError::Unsupported)
        }

        fn irq_disable(_handle: IrqHandle) -> AxResult {
            Err(AxError::Unsupported)
        }
    }
}

#[test]
#[cfg(feature = "pci")]
fn optional_pci_binding_info_can_be_empty() {
    let info = binding_info_from_pci(
        PciInfo {
            address: PciAddress::new(0, 0, 0, 0),
            interrupt_pin: 0,
            interrupt_line: 0,
            intx_route: None,
        },
        PciIrqRequirement::Optional,
    )
    .unwrap();

    assert_eq!(info.irq_num(), None);
}

#[test]
#[cfg(feature = "pci")]
fn required_pci_binding_info_reports_unresolved_irq() {
    let err = binding_info_from_pci(
        PciInfo {
            address: PciAddress::new(0, 0, 0, 0),
            interrupt_pin: 0,
            interrupt_line: 0,
            intx_route: None,
        },
        PciIrqRequirement::Required,
    )
    .unwrap_err();

    assert!(err.to_string().contains("failed to resolve IRQ"));
}

#[test]
fn fdt_binding_info_carries_first_irq_specifier_without_setup() {
    let _guard = RDRIVE_TEST_LOCK.lock().unwrap();
    *CAPTURED_IRQ.lock().unwrap() = None;
    *SETUP_SPECIFIER.lock().unwrap() = None;

    ensure_rdrive_test_intc();
    rdrive::register_add(DriverRegister {
        name: "binding-info-fdt-test-device",
        level: ProbeLevel::PostKernel,
        priority: ProbePriority::DEFAULT,
        probe_kinds: TEST_DEVICE_PROBE_KINDS,
    });
    rdrive::probe_all(true).unwrap();

    let captured = CAPTURED_IRQ.lock().unwrap().clone();
    let Some(Some(BindingIrq::Source(BindingIrqSource::FdtInterrupt(spec)))) = captured else {
        panic!("expected captured FDT interrupt binding");
    };
    assert_eq!(spec.cells, vec![0, 42, 4]);
    let controller = rdrive::fdt_phandle_to_device_id(Phandle::from(1)).unwrap();
    assert_eq!(spec.controller, controller);
    assert_eq!(*SETUP_SPECIFIER.lock().unwrap(), None);
}

#[test]
fn named_fdt_interrupt_binding_selects_matching_specifier() {
    let _guard = RDRIVE_TEST_LOCK.lock().unwrap();
    *CAPTURED_IRQ.lock().unwrap() = None;
    *SETUP_SPECIFIER.lock().unwrap() = None;

    ensure_rdrive_fdt_initialized();

    let irq = rdrive::with_fdt(|fdt| {
        let node = fdt.find_compatible(&["test,binding-info"]).pop().unwrap();
        binding_irq_from_named_fdt_interrupt(&node, "backup")
    })
    .unwrap()
    .unwrap()
    .unwrap();

    let BindingIrq::Source(BindingIrqSource::FdtInterrupt(spec)) = irq else {
        panic!("expected named FDT interrupt binding");
    };
    let controller = rdrive::fdt_phandle_to_device_id(Phandle::from(1)).unwrap();
    assert_eq!(spec.controller, controller);
    assert_eq!(spec.cells, vec![0, 43, 4]);
    assert_eq!(*SETUP_SPECIFIER.lock().unwrap(), None);
}

#[test]
fn acpi_binding_info_preserves_route_without_setup() {
    let _guard = RDRIVE_TEST_LOCK.lock().unwrap();
    *SETUP_ACPI_ROUTE.lock().unwrap() = None;
    ensure_rdrive_test_intc();

    let info = binding_info_from_acpi_route("\\_SB.TEST", Some(acpi_route())).unwrap();

    assert_eq!(info.irq_num(), None);
    assert_eq!(*SETUP_ACPI_ROUTE.lock().unwrap(), None);
    assert_eq!(
        info.irq(),
        Some(&BindingIrq::Source(BindingIrqSource::AcpiGsiRoute(
            irq_framework::AcpiGsiRoute {
                gsi: acpi_route().gsi,
                vector: acpi_route().vector,
                controller: irq_framework::AcpiGsiController::IoApic,
                controller_id: acpi_route().controller_id,
                controller_address: acpi_route().controller_address,
                controller_input: acpi_route().controller_input,
                trigger: irq_framework::AcpiIrqTrigger::Level,
                polarity: irq_framework::AcpiIrqPolarity::ActiveLow,
            }
        )))
    );
}

struct TestIntc;

impl DriverGeneric for TestIntc {
    fn name(&self) -> &str {
        "test-intc"
    }
}

impl rdif_intc::Interface for TestIntc {
    fn supports_acpi_gsi(&self, route: &AcpiGsiRoute) -> bool {
        *route == acpi_route()
    }

    fn translate_fdt(
        &self,
        irq_prop: &[u32],
    ) -> Result<rdif_intc::ControllerIrqTranslation, rdif_intc::IrqError> {
        *SETUP_SPECIFIER.lock().unwrap() = Some(irq_prop.to_vec());
        Ok(rdif_intc::ControllerIrqTranslation::new(
            irq_framework::HwIrq(77),
        ))
    }

    fn translate_acpi(
        &self,
        _route: &AcpiGsiRoute,
    ) -> Result<rdif_intc::ControllerIrqTranslation, rdif_intc::IrqError> {
        Ok(rdif_intc::ControllerIrqTranslation::new(
            irq_framework::HwIrq(88),
        ))
    }

    fn configure_acpi(
        &mut self,
        _translation: &rdif_intc::IrqTranslation,
        route: &AcpiGsiRoute,
    ) -> Result<(), rdif_intc::IrqError> {
        *SETUP_ACPI_ROUTE.lock().unwrap() = Some(*route);
        Ok(())
    }
}

fn register_test_intc(probe: ProbeFdt<'_>) -> Result<(), OnProbeError> {
    probe
        .into_platform_device()
        .register(rdif_intc::Intc::new(TEST_INTC_DOMAIN, TestIntc));
    Ok(())
}

fn ensure_rdrive_fdt_initialized() {
    if !rdrive::is_initialized() {
        let fdt_data = Box::leak(Box::new(minimal_irq_fdt().encode()));
        let fdt_addr = NonNull::new(fdt_data.as_ref().as_ptr() as *mut u8).unwrap();
        rdrive::init(Platform::Fdt { addr: fdt_addr }).unwrap();
    }
}

fn ensure_rdrive_test_intc() {
    ensure_rdrive_fdt_initialized();
    let controller = rdrive::fdt_phandle_to_device_id(Phandle::from(1)).unwrap();
    if rdrive::get::<rdif_intc::Intc>(controller).is_ok() {
        return;
    }
    rdrive::register_add(DriverRegister {
        name: "binding-info-fdt-test-intc",
        level: ProbeLevel::PostKernel,
        priority: ProbePriority::INTC,
        probe_kinds: TEST_INTC_PROBE_KINDS,
    });
    rdrive::probe_all(true).unwrap();
}

fn capture_binding_info(probe: ProbeFdt<'_>) -> Result<(), OnProbeError> {
    *CAPTURED_IRQ.lock().unwrap() = Some(binding_info_from_fdt(probe.info())?.irq_cloned());
    Ok(())
}

fn minimal_irq_fdt() -> Fdt {
    let mut fdt = Fdt::new();
    let root = fdt.root_id();
    fdt.node_mut(root)
        .unwrap()
        .set_property(prop_u32s("#address-cells", &[1]));
    fdt.node_mut(root)
        .unwrap()
        .set_property(prop_u32s("#size-cells", &[1]));

    let intc = fdt.add_node(root, Node::new("interrupt-controller@0"));
    fdt.node_mut(intc).unwrap().set_property(prop_strs(
        "compatible",
        &["test,intc", "test,intc-fallback"],
    ));
    fdt.node_mut(intc)
        .unwrap()
        .set_property(prop_u32s("phandle", &[1]));
    fdt.node_mut(intc)
        .unwrap()
        .set_property(Property::new("interrupt-controller", Vec::new()));
    fdt.node_mut(intc)
        .unwrap()
        .set_property(prop_u32s("#interrupt-cells", &[3]));

    let dev = fdt.add_node(root, Node::new("device@0"));
    fdt.node_mut(dev).unwrap().set_property(prop_strs(
        "compatible",
        &["test,binding-info", "test,binding-info-fallback"],
    ));
    fdt.node_mut(dev)
        .unwrap()
        .set_property(prop_u32s("interrupt-parent", &[1]));
    fdt.node_mut(dev)
        .unwrap()
        .set_property(prop_u32s("interrupts", &[0, 42, 4, 0, 43, 4]));
    fdt.node_mut(dev)
        .unwrap()
        .set_property(prop_strs("interrupt-names", &["main", "backup"]));

    fdt
}

fn acpi_route() -> AcpiGsiRoute {
    AcpiGsiRoute {
        gsi: 32,
        vector: 0x50,
        controller: AcpiGsiController::IoApic,
        controller_id: 0,
        controller_address: 0xfec0_0000,
        controller_input: 32,
        trigger: AcpiIrqTrigger::Level,
        polarity: AcpiIrqPolarity::ActiveLow,
    }
}

fn prop_u32s(name: &str, values: &[u32]) -> Property {
    let mut data = Vec::new();
    for value in values {
        data.extend_from_slice(&value.to_be_bytes());
    }
    Property::new(name, data)
}

fn prop_strs(name: &str, values: &[&str]) -> Property {
    let mut data = Vec::new();
    for value in values {
        data.extend_from_slice(value.as_bytes());
        data.push(0);
    }
    Property::new(name, data)
}
