use alloc::{boxed::Box, collections::VecDeque, format, sync::Arc};
use core::{alloc::Layout, cmp, ptr::NonNull, time::Duration};

use dma_api::{DmaAddr, DmaAllocHandle, DmaConstraints, DmaOp};
use ixgbe_driver::{
    INTEL_82599, INTEL_VEND, IxgbeDevice, IxgbeError, IxgbeHal, IxgbeNetBuf, MemPool, NicDevice,
    PhysAddr,
};
use pcie::CommandRegister;
use rd_net::{DmaBuffer, Event, IRxQueue, ITxQueue, NetError, QueueConfig};
use rdrive::{
    DriverGeneric, PlatformDevice,
    probe::{
        OnProbeError,
        pci::{EndpointRc, FnOnProbe},
    },
};

use crate::net::{PlatformDeviceNet, pci_legacy_irq};

const DRIVER_NAME: &str = "ixgbe";
const QUEUE_SIZE: usize = 512;
const QUEUE_ID: u16 = 0;
const RECV_BATCH_SIZE: usize = 64;
const RX_BUFFER_QUEUE_SIZE: usize = 1024;
const MEM_POOL_ENTRIES: usize = 4096;
const MEM_POOL_ENTRY_SIZE: usize = 2048;
const DMA_MASK: u64 = u64::MAX;

crate::model_register!(
    name: "Intel 82599 PCI Network",
    level: ProbeLevel::PostKernel,
    priority: ProbePriority::DEFAULT,
    probe_kinds: &[ProbeKind::Pci {
        on_probe: probe_pci as FnOnProbe,
    }],
);

fn probe_pci(endpoint: &mut EndpointRc, plat_dev: PlatformDevice) -> Result<(), OnProbeError> {
    if endpoint.vendor_id() != INTEL_VEND || endpoint.device_id() != INTEL_82599 {
        return Err(OnProbeError::NotMatch);
    }

    let address = endpoint.address();
    let irq = pci_legacy_irq(endpoint)
        .ok_or_else(|| OnProbeError::other(format!("failed to resolve IRQ for ixgbe {address}")))?;
    let Some(bar) = endpoint.bar_mmio(0) else {
        return Err(OnProbeError::other("ixgbe BAR0 MMIO region missing"));
    };
    let bar_start = bar.start;
    let bar_len = bar.end.saturating_sub(bar_start);

    endpoint.update_command(|mut cmd| {
        cmd.insert(CommandRegister::MEMORY_ENABLE | CommandRegister::BUS_MASTER_ENABLE);
        cmd.remove(CommandRegister::INTERRUPT_DISABLE);
        cmd
    });

    let mmio = axklib::mmio::ioremap_raw(bar_start.into(), bar_len)
        .map_err(|err| OnProbeError::other(format!("failed to map ixgbe BAR0: {err:?}")))?;

    let dev = IxgbeNet::new(mmio.as_ptr() as usize, bar_len)
        .map_err(|err| OnProbeError::other(format!("failed to initialize ixgbe: {err:?}")))?;
    plat_dev.register_net(DRIVER_NAME, dev, Some(irq));
    log::info!("registered ixgbe PCI network device at {address} with irq {irq:#x}");
    Ok(())
}

struct IxgbeNet {
    inner: Arc<spin::Mutex<IxgbeInner>>,
    tx_created: bool,
    rx_created: bool,
    irq_enabled: bool,
}

impl IxgbeNet {
    fn new(base: usize, len: usize) -> Result<Self, IxgbeError> {
        let mem_pool = MemPool::allocate::<IxgbeOsHal>(MEM_POOL_ENTRIES, MEM_POOL_ENTRY_SIZE)?;
        let device = IxgbeDevice::<IxgbeOsHal, QUEUE_SIZE>::init(
            base,
            len,
            QUEUE_ID + 1,
            QUEUE_ID + 1,
            &mem_pool,
        )?;
        Ok(Self {
            inner: Arc::new(spin::Mutex::new(IxgbeInner {
                device,
                mem_pool,
                rx_ready: VecDeque::with_capacity(RX_BUFFER_QUEUE_SIZE),
                rx_buffers: VecDeque::with_capacity(RX_BUFFER_QUEUE_SIZE),
                tx_done: VecDeque::with_capacity(QUEUE_SIZE),
            })),
            tx_created: false,
            rx_created: false,
            irq_enabled: false,
        })
    }
}

impl DriverGeneric for IxgbeNet {
    fn name(&self) -> &str {
        DRIVER_NAME
    }
}

impl rd_net::Interface for IxgbeNet {
    fn mac_address(&self) -> [u8; 6] {
        self.inner.lock().device.get_mac_addr()
    }

    fn create_tx_queue(&mut self) -> Option<alloc::boxed::Box<dyn ITxQueue>> {
        if self.tx_created {
            return None;
        }
        self.tx_created = true;
        Some(Box::new(IxgbeTxQueue {
            inner: self.inner.clone(),
        }))
    }

    fn create_rx_queue(&mut self) -> Option<alloc::boxed::Box<dyn IRxQueue>> {
        if self.rx_created {
            return None;
        }
        self.rx_created = true;
        Some(Box::new(IxgbeRxQueue {
            inner: self.inner.clone(),
        }))
    }

    fn enable_irq(&mut self) {
        self.irq_enabled = true;
    }

    fn disable_irq(&mut self) {
        self.irq_enabled = false;
    }

    fn is_irq_enabled(&self) -> bool {
        self.irq_enabled
    }

    fn handle_irq(&mut self) -> Event {
        let mut event = Event::none();
        event.tx_queue.insert(QUEUE_ID as usize);
        event.rx_queue.insert(QUEUE_ID as usize);
        event
    }
}

struct IxgbeInner {
    device: IxgbeDevice<IxgbeOsHal, QUEUE_SIZE>,
    mem_pool: Arc<MemPool>,
    rx_ready: VecDeque<IxgbeNetBuf>,
    rx_buffers: VecDeque<RuntimeNetBuffer>,
    tx_done: VecDeque<u64>,
}

unsafe impl Send for IxgbeInner {}

#[derive(Clone, Copy)]
struct RuntimeNetBuffer {
    virt: usize,
    bus_addr: u64,
    len: usize,
}

impl From<DmaBuffer> for RuntimeNetBuffer {
    fn from(buffer: DmaBuffer) -> Self {
        Self {
            virt: buffer.virt.as_ptr() as usize,
            bus_addr: buffer.bus_addr,
            len: buffer.len,
        }
    }
}

struct IxgbeTxQueue {
    inner: Arc<spin::Mutex<IxgbeInner>>,
}

impl ITxQueue for IxgbeTxQueue {
    fn id(&self) -> usize {
        QUEUE_ID as usize
    }

    fn config(&self) -> QueueConfig {
        ixgbe_queue_config()
    }

    fn submit(&mut self, buffer: DmaBuffer) -> Result<(), NetError> {
        let mut inner = self.inner.lock();
        let _ = inner.device.recycle_tx_buffers(QUEUE_ID);
        let mut tx = IxgbeNetBuf::alloc(&inner.mem_pool, buffer.len).map_err(map_error)?;
        let source = unsafe { core::slice::from_raw_parts(buffer.virt.as_ptr(), buffer.len) };
        tx.packet_mut().copy_from_slice(source);
        inner.device.send(QUEUE_ID, tx).map_err(map_error)?;
        inner.tx_done.push_back(buffer.bus_addr);
        Ok(())
    }

    fn reclaim(&mut self) -> Option<u64> {
        let mut inner = self.inner.lock();
        let _ = inner.device.recycle_tx_buffers(QUEUE_ID);
        inner.tx_done.pop_front()
    }
}

struct IxgbeRxQueue {
    inner: Arc<spin::Mutex<IxgbeInner>>,
}

impl IRxQueue for IxgbeRxQueue {
    fn id(&self) -> usize {
        QUEUE_ID as usize
    }

    fn config(&self) -> QueueConfig {
        ixgbe_queue_config()
    }

    fn submit(&mut self, buffer: DmaBuffer) -> Result<(), NetError> {
        self.inner.lock().rx_buffers.push_back(buffer.into());
        Ok(())
    }

    fn reclaim(&mut self) -> Option<(u64, usize)> {
        let mut inner = self.inner.lock();
        if inner.rx_buffers.is_empty() {
            return None;
        }

        if inner.rx_ready.is_empty() {
            let mut received = VecDeque::with_capacity(RECV_BATCH_SIZE);
            inner
                .device
                .receive_packets(QUEUE_ID, RECV_BATCH_SIZE, |packet| {
                    received.push_back(packet);
                })
                .ok()?;
            inner.rx_ready.extend(received);
        }

        let packet = inner.rx_ready.pop_front()?;
        let buffer = inner.rx_buffers.pop_front()?;
        let len = cmp::min(packet.packet_len(), buffer.len);
        unsafe {
            core::ptr::copy_nonoverlapping(packet.packet().as_ptr(), buffer.virt as *mut u8, len);
        }
        let bus_addr = buffer.bus_addr;
        drop(packet);
        Some((bus_addr, len))
    }
}

fn ixgbe_queue_config() -> QueueConfig {
    QueueConfig {
        dma_mask: DMA_MASK,
        align: 0x1000,
        buf_size: MEM_POOL_ENTRY_SIZE,
        ring_size: QUEUE_SIZE,
    }
}

fn map_error(err: IxgbeError) -> NetError {
    match err {
        IxgbeError::QueueFull | IxgbeError::NotReady => NetError::Retry,
        IxgbeError::NoMemory => NetError::NoMemory,
        IxgbeError::QueueNotAligned | IxgbeError::PageNotAligned | IxgbeError::InvalidQueue => {
            NetError::Other(Box::new(rd_net::KError::Unknown("ixgbe error")))
        }
    }
}

struct IxgbeOsHal;

unsafe impl IxgbeHal for IxgbeOsHal {
    fn dma_alloc(size: usize) -> (PhysAddr, NonNull<u8>) {
        let layout =
            Layout::from_size_align(size.max(1), 0x1000).expect("ixgbe DMA layout should be valid");
        let handle =
            unsafe { axklib::dma::op().alloc_coherent(DmaConstraints::new(DMA_MASK), layout) }
                .expect("ixgbe DMA allocation failed");
        let paddr = handle.dma_addr().as_u64() as usize;
        let vaddr = handle.as_ptr();
        (paddr, vaddr)
    }

    unsafe fn dma_dealloc(paddr: PhysAddr, vaddr: NonNull<u8>, size: usize) -> i32 {
        let Ok(layout) = Layout::from_size_align(size.max(1), 0x1000) else {
            return -1;
        };
        let handle = unsafe { DmaAllocHandle::new(vaddr, DmaAddr::from(paddr as u64), layout) };
        unsafe { axklib::dma::op().dealloc_coherent(handle) };
        0
    }

    unsafe fn mmio_phys_to_virt(paddr: PhysAddr, size: usize) -> NonNull<u8> {
        axklib::mmio::ioremap_raw(paddr.into(), size)
            .expect("ixgbe MMIO mapping failed")
            .as_nonnull_ptr()
    }

    unsafe fn mmio_virt_to_phys(vaddr: NonNull<u8>, _size: usize) -> PhysAddr {
        axklib::mem::virt_to_phys((vaddr.as_ptr() as usize).into()).as_usize()
    }

    fn wait_until(duration: Duration) -> Result<(), &'static str> {
        axklib::time::busy_wait(duration);
        Ok(())
    }
}
