//! Virtual machine types

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;

/// Unique identifier for a VM
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct VmId(Uuid);

impl VmId {
    /// Create a new random VM ID
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }

    /// Create from a UUID
    pub fn from_uuid(uuid: Uuid) -> Self {
        Self(uuid)
    }

    /// Get the underlying UUID
    pub fn as_uuid(&self) -> &Uuid {
        &self.0
    }
}

impl Default for VmId {
    fn default() -> Self {
        Self::new()
    }
}

impl std::fmt::Display for VmId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// VM lifecycle state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VmState {
    /// Awaiting scheduling
    Pending,
    /// Resources being provisioned
    Creating,
    /// Created but not running
    Stopped,
    /// Boot in progress
    Starting,
    /// Active and healthy
    Running,
    /// Graceful shutdown
    Stopping,
    /// Live migration in progress
    Migrating,
    /// Recoverable error
    Error,
    /// Terminal failure
    Failed,
    /// Soft-deleted, pending cleanup
    Deleted,
}

impl VmState {
    /// Check if the VM is in a terminal state
    pub fn is_terminal(&self) -> bool {
        matches!(self, VmState::Failed | VmState::Deleted)
    }

    /// Check if the VM can be started
    pub fn can_start(&self) -> bool {
        matches!(self, VmState::Stopped | VmState::Error)
    }

    /// Check if the VM can be stopped
    pub fn can_stop(&self) -> bool {
        matches!(self, VmState::Running | VmState::Starting)
    }
}

/// Hypervisor backend type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum HypervisorType {
    /// QEMU/KVM - full-featured
    Kvm,
    /// AWS Firecracker - microVMs
    Firecracker,
    /// mvisor - lightweight
    Mvisor,
}

/// CPU specification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CpuSpec {
    /// Number of vCPUs
    pub vcpus: u32,
    /// Cores per socket
    pub cores_per_socket: u32,
    /// Number of sockets
    pub sockets: u32,
    /// CPU model (e.g., "host-passthrough")
    pub cpu_model: Option<String>,
}

impl Default for CpuSpec {
    fn default() -> Self {
        Self {
            vcpus: 1,
            cores_per_socket: 1,
            sockets: 1,
            cpu_model: None,
        }
    }
}

/// Memory specification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemorySpec {
    /// Memory size in MiB
    pub size_mib: u64,
    /// Use huge pages
    pub hugepages: bool,
}

impl Default for MemorySpec {
    fn default() -> Self {
        Self {
            size_mib: 512,
            hugepages: false,
        }
    }
}

/// Disk bus type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DiskBus {
    Virtio,
    Scsi,
    Ide,
    Sata,
}

/// Disk cache mode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DiskCache {
    None,
    Writeback,
    Writethrough,
}

/// Managed volume driver kind
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VolumeDriverKind {
    Managed,
    CephRbd,
}

impl Default for VolumeDriverKind {
    fn default() -> Self {
        VolumeDriverKind::Managed
    }
}

/// Managed volume format
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VolumeFormat {
    Raw,
    Qcow2,
}

impl Default for VolumeFormat {
    fn default() -> Self {
        VolumeFormat::Raw
    }
}

/// Managed volume lifecycle
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum VolumeStatus {
    Pending,
    Available,
    InUse,
    Error,
}

impl Default for VolumeStatus {
    fn default() -> Self {
        VolumeStatus::Pending
    }
}

/// Disk source
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DiskSource {
    /// Boot from image
    Image { image_id: String },
    /// Attach existing volume
    Volume { volume_id: String },
    /// Create blank disk
    Blank,
}

/// Disk specification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiskSpec {
    /// Disk identifier
    pub id: String,
    /// Source (image, volume, or blank)
    pub source: DiskSource,
    /// Disk size in GiB
    pub size_gib: u64,
    /// Bus type
    pub bus: DiskBus,
    /// Cache mode
    pub cache: DiskCache,
    /// Boot order (lower = higher priority)
    pub boot_index: Option<u32>,
}

impl Default for DiskSpec {
    fn default() -> Self {
        Self {
            id: String::new(),
            source: DiskSource::Blank,
            size_gib: 10,
            bus: DiskBus::Virtio,
            cache: DiskCache::Writeback,
            boot_index: None,
        }
    }
}

/// NIC model type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NicModel {
    VirtioNet,
    E1000,
}

/// Network interface specification
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NetworkSpec {
    /// Interface identifier
    pub id: String,
    /// Overlay network ID
    pub network_id: String,
    /// PrismNET subnet ID (optional, for OVN integration)
    pub subnet_id: Option<String>,
    /// PrismNET port ID (optional, for OVN integration)
    pub port_id: Option<String>,
    /// MAC address (auto-generated if None)
    pub mac_address: Option<String>,
    /// IP address (DHCP if None)
    pub ip_address: Option<String>,
    /// Attached subnet CIDR (required for the local KVM dataplane)
    pub cidr_block: Option<String>,
    /// Attached subnet gateway IP (required for the local KVM dataplane)
    pub gateway_ip: Option<String>,
    /// Whether DHCP should be enabled on the attached subnet
    pub dhcp_enabled: bool,
    /// NIC model
    pub model: NicModel,
    /// Security groups
    pub security_groups: Vec<String>,
}

impl Default for NetworkSpec {
    fn default() -> Self {
        Self {
            id: String::new(),
            network_id: "default".to_string(),
            subnet_id: None,
            port_id: None,
            mac_address: None,
            ip_address: None,
            cidr_block: None,
            gateway_ip: None,
            dhcp_enabled: false,
            model: NicModel::VirtioNet,
            security_groups: Vec::new(),
        }
    }
}

/// Boot specification
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct BootSpec {
    /// Kernel path (direct kernel boot)
    pub kernel: Option<String>,
    /// Initrd path
    pub initrd: Option<String>,
    /// Kernel command line
    pub cmdline: Option<String>,
}

/// Security specification
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SecuritySpec {
    /// Enable secure boot
    pub secure_boot: bool,
    /// Enable TPM
    pub tpm: bool,
}

/// Complete VM specification
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct VmSpec {
    /// CPU configuration
    pub cpu: CpuSpec,
    /// Memory configuration
    pub memory: MemorySpec,
    /// Disk configurations
    pub disks: Vec<DiskSpec>,
    /// Network configurations
    pub network: Vec<NetworkSpec>,
    /// Boot configuration
    pub boot: BootSpec,
    /// Security configuration
    pub security: SecuritySpec,
}

/// Resource usage statistics
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ResourceUsage {
    /// CPU usage percentage
    pub cpu_percent: f64,
    /// Memory used in MiB
    pub memory_used_mib: u64,
    /// Disk read bytes
    pub disk_read_bytes: u64,
    /// Disk write bytes
    pub disk_write_bytes: u64,
    /// Network receive bytes
    pub network_rx_bytes: u64,
    /// Network transmit bytes
    pub network_tx_bytes: u64,
}

/// VM runtime status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VmStatus {
    /// Actual running state
    pub actual_state: VmState,
    /// Hypervisor process PID
    pub host_pid: Option<u32>,
    /// Last boot timestamp (Unix epoch)
    pub started_at: Option<u64>,
    /// Assigned IP addresses
    pub ip_addresses: Vec<String>,
    /// Resource usage
    pub resource_usage: ResourceUsage,
    /// Last error message
    pub last_error: Option<String>,
}

impl Default for VmStatus {
    fn default() -> Self {
        Self {
            actual_state: VmState::Pending,
            host_pid: None,
            started_at: None,
            ip_addresses: Vec::new(),
            resource_usage: ResourceUsage::default(),
            last_error: None,
        }
    }
}

/// Driver-specific backing for a persistent VM volume.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum VolumeBacking {
    Managed,
    CephRbd {
        cluster_id: String,
        pool: String,
        image: String,
    },
}

impl Default for VolumeBacking {
    fn default() -> Self {
        VolumeBacking::Managed
    }
}

/// Persistent VM volume metadata.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Volume {
    /// Unique identifier
    pub id: String,
    /// Human-friendly name
    pub name: String,
    /// Organization owner
    pub org_id: String,
    /// Project owner
    pub project_id: String,
    /// Requested size in GiB
    pub size_gib: u64,
    /// Backing driver
    pub driver: VolumeDriverKind,
    /// Placement / QoS class
    pub storage_class: String,
    /// Guest-visible disk format
    pub format: VolumeFormat,
    /// Current lifecycle state
    pub status: VolumeStatus,
    /// Driver-specific backing metadata
    pub backing: VolumeBacking,
    /// Attached VM, if any
    pub attached_to_vm: Option<String>,
    /// Node currently holding the writer attachment, if any
    pub attached_to_node: Option<String>,
    /// Monotonic generation incremented whenever attachment ownership changes
    pub attachment_generation: u64,
    /// Attachment generation last flushed back to the authoritative backing store
    pub last_flushed_attachment_generation: u64,
    /// Creation timestamp (Unix epoch)
    pub created_at: u64,
    /// Last update timestamp (Unix epoch)
    pub updated_at: u64,
    /// Custom metadata
    pub metadata: HashMap<String, String>,
    /// Labels for filtering
    pub labels: HashMap<String, String>,
}

impl Volume {
    /// Create a new volume record with sane defaults.
    pub fn new(
        id: impl Into<String>,
        name: impl Into<String>,
        org_id: impl Into<String>,
        project_id: impl Into<String>,
        size_gib: u64,
    ) -> Self {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();
        Self {
            id: id.into(),
            name: name.into(),
            org_id: org_id.into(),
            project_id: project_id.into(),
            size_gib,
            driver: VolumeDriverKind::Managed,
            storage_class: "managed-default".to_string(),
            format: VolumeFormat::Raw,
            status: VolumeStatus::Pending,
            backing: VolumeBacking::Managed,
            attached_to_vm: None,
            attached_to_node: None,
            attachment_generation: 0,
            last_flushed_attachment_generation: 0,
            created_at: now,
            updated_at: now,
            metadata: HashMap::new(),
            labels: HashMap::new(),
        }
    }
}

/// Resolved disk attachment handed to the hypervisor backend.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum DiskAttachment {
    File {
        path: String,
        format: VolumeFormat,
    },
    Nbd {
        uri: String,
        format: VolumeFormat,
    },
    CephRbd {
        pool: String,
        image: String,
        monitors: Vec<String>,
        user: String,
        secret: Option<String>,
    },
}

/// Hypervisor-ready disk descriptor.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AttachedDisk {
    /// Disk identifier
    pub id: String,
    /// Resolved attachment descriptor
    pub attachment: DiskAttachment,
    /// Guest bus type
    pub bus: DiskBus,
    /// Host cache mode
    pub cache: DiskCache,
    /// Guest boot order (lower = higher priority)
    pub boot_index: Option<u32>,
    /// Read-only attachment
    pub read_only: bool,
}

/// Image visibility
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Visibility {
    Public,
    Private,
    Shared,
}

impl Default for Visibility {
    fn default() -> Self {
        Visibility::Private
    }
}

/// Image disk format
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ImageFormat {
    Raw,
    Qcow2,
    Vmdk,
    Vhd,
}

impl Default for ImageFormat {
    fn default() -> Self {
        ImageFormat::Qcow2
    }
}

/// Guest operating system type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum OsType {
    Unspecified,
    Linux,
    Windows,
    Bsd,
}

impl Default for OsType {
    fn default() -> Self {
        OsType::Unspecified
    }
}

/// Guest architecture
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Architecture {
    Unspecified,
    X86_64,
    Aarch64,
}

impl Default for Architecture {
    fn default() -> Self {
        Architecture::Unspecified
    }
}

/// Image lifecycle status
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ImageStatus {
    Pending,
    Uploading,
    Available,
    Error,
}

impl Default for ImageStatus {
    fn default() -> Self {
        ImageStatus::Pending
    }
}

/// VM boot image metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Image {
    /// Unique identifier
    pub id: String,
    /// Human-friendly name
    pub name: String,
    /// Organization owner
    pub org_id: String,
    /// Project owner for tenant-scoped access control
    pub project_id: String,
    /// Visibility policy
    pub visibility: Visibility,
    /// Materialized image format
    pub format: ImageFormat,
    /// Object size in bytes
    pub size_bytes: u64,
    /// sha256 checksum of the stored image object
    pub checksum: String,
    /// Guest operating system
    pub os_type: OsType,
    /// Guest OS version string
    pub os_version: String,
    /// CPU architecture
    pub architecture: Architecture,
    /// Minimum disk requirement
    pub min_disk_gib: u32,
    /// Minimum memory requirement
    pub min_memory_mib: u32,
    /// Current import status
    pub status: ImageStatus,
    /// Creation timestamp (Unix epoch)
    pub created_at: u64,
    /// Last update timestamp (Unix epoch)
    pub updated_at: u64,
    /// User metadata
    pub metadata: HashMap<String, String>,
}

impl Image {
    /// Create a new image record with generated identifiers and timestamps.
    pub fn new(
        name: impl Into<String>,
        org_id: impl Into<String>,
        project_id: impl Into<String>,
    ) -> Self {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        Self {
            id: Uuid::new_v4().to_string(),
            name: name.into(),
            org_id: org_id.into(),
            project_id: project_id.into(),
            visibility: Visibility::Private,
            format: ImageFormat::Qcow2,
            size_bytes: 0,
            checksum: String::new(),
            os_type: OsType::Unspecified,
            os_version: String::new(),
            architecture: Architecture::Unspecified,
            min_disk_gib: 0,
            min_memory_mib: 0,
            status: ImageStatus::Pending,
            created_at: now,
            updated_at: now,
            metadata: HashMap::new(),
        }
    }
}

/// Unique identifier for a node
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct NodeId(String);

impl NodeId {
    /// Create from string
    pub fn new(id: impl Into<String>) -> Self {
        Self(id.into())
    }

    /// Get as string slice
    pub fn as_str(&self) -> &str {
        &self.0
    }
}

impl std::fmt::Display for NodeId {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// Node lifecycle state
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NodeState {
    /// Ready to run workloads
    Ready,
    /// Not ready (unhealthy)
    NotReady,
    /// Scheduling disabled
    Cordoned,
    /// Draining workloads
    Draining,
    /// Maintenance mode
    Maintenance,
}

impl Default for NodeState {
    fn default() -> Self {
        NodeState::NotReady
    }
}

/// Capacity summary for a node
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NodeCapacity {
    /// Total vCPUs
    pub vcpus: u32,
    /// Total memory in MiB
    pub memory_mib: u64,
    /// Total storage in GiB
    pub storage_gib: u64,
}

/// Node record
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Node {
    /// Unique identifier
    pub id: NodeId,
    /// Human-friendly name
    pub name: String,
    /// Node state
    pub state: NodeState,
    /// Total capacity
    pub capacity: NodeCapacity,
    /// Allocatable capacity
    pub allocatable: NodeCapacity,
    /// Allocated capacity
    pub allocated: NodeCapacity,
    /// Supported hypervisors
    pub hypervisors: Vec<HypervisorType>,
    /// Supported persistent volume drivers
    pub supported_volume_drivers: Vec<VolumeDriverKind>,
    /// Supported storage classes
    pub supported_storage_classes: Vec<String>,
    /// Whether the node can participate in shared-storage live migration
    pub shared_live_migration: bool,
    /// Node labels
    pub labels: HashMap<String, String>,
    /// Agent version string
    pub agent_version: String,
    /// Last heartbeat timestamp (Unix epoch)
    pub last_heartbeat: u64,
}

impl Node {
    /// Create a new node record
    pub fn new(id: impl Into<String>) -> Self {
        Self {
            id: NodeId::new(id),
            name: String::new(),
            state: NodeState::default(),
            capacity: NodeCapacity::default(),
            allocatable: NodeCapacity::default(),
            allocated: NodeCapacity::default(),
            hypervisors: Vec::new(),
            supported_volume_drivers: vec![VolumeDriverKind::Managed],
            supported_storage_classes: vec!["managed-default".to_string()],
            shared_live_migration: true,
            labels: HashMap::new(),
            agent_version: String::new(),
            last_heartbeat: 0,
        }
    }
}

/// Virtual machine record
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VirtualMachine {
    /// Unique identifier
    pub id: VmId,
    /// User-defined name
    pub name: String,
    /// Organization owner
    pub org_id: String,
    /// Project owner
    pub project_id: String,
    /// Current state
    pub state: VmState,
    /// Desired configuration
    pub spec: VmSpec,
    /// Runtime status
    pub status: VmStatus,
    /// Assigned node
    pub node_id: Option<NodeId>,
    /// Hypervisor backend type
    pub hypervisor: HypervisorType,
    /// Creation timestamp (Unix epoch)
    pub created_at: u64,
    /// Last update timestamp (Unix epoch)
    pub updated_at: u64,
    /// Creator principal ID
    pub created_by: String,
    /// Custom metadata
    pub metadata: HashMap<String, String>,
    /// Labels for filtering
    pub labels: HashMap<String, String>,
}

impl VirtualMachine {
    /// Create a new VM with the given name and spec
    pub fn new(
        name: impl Into<String>,
        org_id: impl Into<String>,
        project_id: impl Into<String>,
        spec: VmSpec,
    ) -> Self {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_secs();

        Self {
            id: VmId::new(),
            name: name.into(),
            org_id: org_id.into(),
            project_id: project_id.into(),
            state: VmState::Pending,
            spec,
            status: VmStatus::default(),
            node_id: None,
            hypervisor: HypervisorType::Kvm,
            created_at: now,
            updated_at: now,
            created_by: String::new(),
            metadata: HashMap::new(),
            labels: HashMap::new(),
        }
    }
}

/// Handle to a created VM for hypervisor operations
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VmHandle {
    /// VM identifier
    pub vm_id: VmId,
    /// Runtime directory path
    pub runtime_dir: String,
    /// Process ID (if running)
    pub pid: Option<u32>,
    /// Backend-specific state
    pub backend_state: HashMap<String, String>,
    /// Disks resolved for the running hypervisor instance
    #[serde(default)]
    pub attached_disks: Vec<AttachedDisk>,
}

impl VmHandle {
    /// Create a new VM handle
    pub fn new(vm_id: VmId, runtime_dir: impl Into<String>) -> Self {
        Self {
            vm_id,
            runtime_dir: runtime_dir.into(),
            pid: None,
            backend_state: HashMap::new(),
            attached_disks: Vec::new(),
        }
    }
}
