use alloc::boxed::Box;
use core::time::Duration;

use ax_crate_interface::impl_interface;
use ax_errno::AxResult;
use ax_memory_addr::{PhysAddr, VirtAddr};
use axvm_types::{InterruptVector, VCpuId, VMId};
use x86_vcpu::host::X86VcpuHostIf;
use x86_vlapic::host::X86VlapicHostIf;

use super::{
    ArchOps, BoundVcpuExit, LegacyDeferredRunWork, VcpuCreateContext, VcpuRunAction,
    VcpuSetupContext,
};
use crate::{
    host::{HostConsole, HostMemory, HostTime, default_host},
    manager,
    vcpu::get_current_vcpu,
};

mod npt;

pub(crate) struct X86_64Arch;

pub(crate) struct X86VcpuCreateState;

impl ArchOps for X86_64Arch {
    type VCpu = x86_vcpu::X86ArchVCpu;
    type PerCpu = x86_vcpu::X86ArchPerCpuState;
    type VcpuCreateState = X86VcpuCreateState;
    type DeferredRunWork = LegacyDeferredRunWork;
    type NestedPageTable = npt::NestedPageTable<crate::HostPagingHandler>;

    fn has_hardware_support() -> bool {
        x86_vcpu::has_hardware_support()
    }

    fn new_vcpu_create_state(
        _vcpu_mappings: &[(usize, Option<usize>, usize)],
    ) -> AxResult<Self::VcpuCreateState> {
        Ok(X86VcpuCreateState)
    }

    fn build_vcpu_create_config(
        _state: &Self::VcpuCreateState,
        _ctx: VcpuCreateContext,
    ) -> AxResult<<Self::VCpu as axvm_types::VmArchVcpuOps>::CreateConfig> {
        Ok(x86_vcpu::X86VCpuCreateConfig)
    }

    fn build_vcpu_setup_config(
        ctx: VcpuSetupContext<'_>,
    ) -> AxResult<<Self::VCpu as axvm_types::VmArchVcpuOps>::SetupConfig> {
        let mut config = x86_vcpu::X86VCpuSetupConfig {
            emulate_com1: ctx.emulates_console,
            ..Default::default()
        };
        for port in ctx.passthrough_ports {
            config.add_passthrough_port_range(port.base, port.length)?;
        }
        Ok(config)
    }

    fn new_nested_page_table(levels: usize) -> AxResult<Self::NestedPageTable> {
        npt::NestedPageTable::new(levels)
    }

    fn before_first_run(vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef<Self::VCpu>) {
        crate::runtime::x86_irq::enable_ioapic_irq_forwarding(vm, vcpu);
    }

    fn before_vcpu_run(vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef<Self::VCpu>) {
        crate::runtime::x86_irq::drain_pending_ioapic_irqs(vm, vcpu);
        crate::runtime::x86_irq::activate_ready_ioapic_forwarding_routes(vm);
    }

    fn after_external_interrupt(
        vm: &crate::AxVMRef,
        vcpu: &crate::vm::AxVCpuRef<Self::VCpu>,
        vector: usize,
    ) {
        crate::host::arceos::dispatch_host_irq(vector);
        crate::check_timer_events();
        crate::runtime::x86_irq::inject_pending_serial_irq(vm, vcpu);
    }

    fn after_preemption_timer(vm: &crate::AxVMRef, vcpu: &crate::vm::AxVCpuRef<Self::VCpu>) {
        crate::timer::check_events();
        crate::runtime::x86_irq::inject_due_pit_irq0(vm, vcpu);
        crate::runtime::x86_irq::inject_pending_serial_irq(vm, vcpu);
    }

    fn after_interrupt_end(
        vm: &crate::AxVMRef,
        vcpu: &crate::vm::AxVCpuRef<Self::VCpu>,
        vector: Option<u8>,
    ) {
        if let Some(vector) = vector {
            crate::runtime::x86_irq::inject_pending_ioapic_irq_after_eoi(vm, vcpu, vector);
        }
    }

    fn handle_halt() -> VcpuRunAction {
        VcpuRunAction::Yield
    }

    fn on_last_vcpu_exit(vm_id: usize) {
        crate::runtime::x86_irq::disable_ioapic_irq_forwarding_for_vm(vm_id);
    }

    fn handle_vcpu_exit_bound(
        vm: &crate::AxVMRef,
        vcpu: &crate::vm::AxVCpuRef<Self::VCpu>,
        exit: <Self::VCpu as axvm_types::VmArchVcpuOps>::Exit,
    ) -> AxResult<BoundVcpuExit<Self::DeferredRunWork>> {
        super::handle_transitional_vm_exit::<Self>(vm, vcpu, exit)
    }

    fn finish_deferred_run_work(
        vm: &crate::AxVMRef,
        vcpu: &crate::vm::AxVCpuRef<Self::VCpu>,
        work: Self::DeferredRunWork,
    ) -> AxResult<VcpuRunAction> {
        super::finish_legacy_deferred_run_work::<Self>(vm, vcpu, work)
    }
}

struct X86VcpuHostIfImpl;

#[impl_interface]
impl X86VcpuHostIf for X86VcpuHostIfImpl {
    fn alloc_frame() -> Option<PhysAddr> {
        default_host().alloc_frame()
    }

    fn dealloc_frame(paddr: PhysAddr) {
        default_host().dealloc_frame(paddr);
    }

    fn alloc_contiguous_frames(frame_count: usize, frame_align: usize) -> Option<PhysAddr> {
        default_host().alloc_contiguous_frames(frame_count, frame_align)
    }

    fn dealloc_contiguous_frames(start_paddr: PhysAddr, frame_count: usize) {
        default_host().dealloc_contiguous_frames(start_paddr, frame_count);
    }

    fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
        default_host().phys_to_virt(paddr)
    }

    fn nanos_to_ticks(nanos: u64) -> u64 {
        default_host().nanos_to_ticks(nanos)
    }
}

struct X86VlapicHostIfImpl;

#[impl_interface]
impl X86VlapicHostIf for X86VlapicHostIfImpl {
    fn alloc_frame() -> Option<PhysAddr> {
        default_host().alloc_frame()
    }

    fn dealloc_frame(paddr: PhysAddr) {
        default_host().dealloc_frame(paddr);
    }

    fn phys_to_virt(paddr: PhysAddr) -> VirtAddr {
        default_host().phys_to_virt(paddr)
    }

    fn virt_to_phys(vaddr: VirtAddr) -> PhysAddr {
        default_host().virt_to_phys(vaddr)
    }

    fn current_time() -> Duration {
        default_host().monotonic_time()
    }

    fn current_time_nanos() -> u64 {
        default_host().monotonic_time().as_nanos() as u64
    }

    fn register_timer(
        deadline: Duration,
        callback: Box<dyn FnOnce(Duration) + Send + 'static>,
    ) -> usize {
        default_host().register_timer(deadline.as_nanos() as u64, callback)
    }

    fn cancel_timer(token: usize) {
        default_host().cancel_timer(token);
    }

    fn write_bytes(bytes: &[u8]) {
        default_host().write_bytes(bytes);
    }

    fn read_bytes(bytes: &mut [u8]) -> usize {
        default_host().read_bytes(bytes)
    }

    fn current_vm_id() -> VMId {
        get_current_vcpu::<x86_vcpu::X86ArchVCpu>()
            .expect("current x86 vCPU is not set")
            .vm_id()
    }

    fn current_vm_vcpu_num() -> usize {
        let vm_id = Self::current_vm_id();
        manager::with_vm(vm_id, |vm| vm.vcpu_num()).unwrap_or(0)
    }

    fn current_vm_active_vcpus() -> usize {
        manager::active_vcpu_mask(Self::current_vm_id()).unwrap_or(0)
    }

    fn active_vcpus(vm_id: VMId) -> Option<usize> {
        manager::active_vcpu_mask(vm_id)
    }

    fn inject_interrupt(vm_id: VMId, vcpu_id: VCpuId, vector: InterruptVector) -> AxResult {
        manager::inject_interrupt(vm_id, vcpu_id, vector as usize)
    }
}
