import os
from enum import StrEnum
from pathlib import Path
from typing import final

from .file_operations import read_file
import logging

logger = logging.getLogger(__name__)


class BusType(StrEnum):
    PCI = "pci"
    USB = "usb"


def get_sysfs_path(relative_path: str | Path) -> Path:
    """Return a sysfs path, optionally rooted below TUXEDO_SYSFS_ROOT for E2E tests."""
    root = Path(os.environ.get("TUXEDO_SYSFS_ROOT", "/"))
    relative = Path(relative_path)
    if relative.is_absolute():
        relative = relative.relative_to("/")
    return root / relative


def get_pci_devices() -> list[str]:
    return _get_pci_usb_devices(BusType.PCI, "vendor", "device", "class")


def get_usb_devices() -> list[str]:
    return _get_pci_usb_devices(BusType.USB, "idVendor", "idProduct")


def _get_pci_usb_devices(
    bus_type: BusType,
    vendor_file_name: str,
    device_file_name: str,
    class_file_name: str | None = None,
) -> list[str]:
    """
    Returns a list of hardware ID strings for PCI or USB.

    USB:
        vendor:device
        Example: ["046d:c534"]

    PCI without class:
        vendor:device
        Example: ["8086:1911", "10ec:8168"]

    PCI with class:
        class:vendor:device
        Example: ["0300:10de:28e0"]

    For PCI devices both formats are returned to keep old configs compatible.
    """

    base = get_sysfs_path(f"/sys/bus/{bus_type}/devices/")
    logger.debug(f"Inspecting bus_type path: {base}")

    if not base.exists():
        logger.warning(f"Bus path does not exist: {base}")
        return []

    devices: list[str] = []

    for dev in base.iterdir():
        if not dev.is_dir():
            continue

        vendor_file = dev / vendor_file_name
        device_file = dev / device_file_name

        vendor = read_file(vendor_file)
        device = read_file(device_file)

        if vendor and device:
            vendor = vendor.lower()
            device = device.lower()

            vendor = vendor.removeprefix("0x")
            device = device.removeprefix("0x")

            # ensure 4-digit hex formatting
            vendor = vendor.zfill(4)
            device = device.zfill(4)

            id_str = f"{vendor}:{device}"
            devices.append(id_str)
            logger.debug(f"Found device: {id_str}")

            if class_file_name:
                class_file = dev / class_file_name
                pci_class = read_file(class_file)

                if pci_class:
                    pci_class = pci_class.lower().removeprefix("0x").zfill(6)

                    # Keep only base class + subclass, e.g.:
                    # 030000 -> 0300
                    # 030200 -> 0302
                    pci_class = pci_class[:4]

                    class_vendor_device_id = f"{pci_class}:{vendor}:{device}"
                    devices.append(class_vendor_device_id)
                    logger.debug(f"Found device with class: {class_vendor_device_id}")

    logger.debug(f"Collected {len(devices)} {bus_type.upper()} devices")

    return devices


def count_gpu_devices() -> int:
    """
    Counts GPUs by looking at PCI class files for:
    - 0x0300: VGA compatible controller
    - 0x0302: 3D controller
    """
    base = get_sysfs_path("/sys/bus/pci/devices")
    if not base.exists():
        logger.warning("PCI devices path missing")
        return 0

    count = 0
    gpu_classes = ("0x0300", "0x0302")

    for dev in base.iterdir():
        class_file = dev / "class"
        class_hex = read_file(class_file)

        if class_hex and class_hex.startswith(gpu_classes):
            count += 1
            logger.debug(f"GPU detected: {dev.name} ({class_hex})")

    logger.info(f"Detected {count} GPU(s)")
    return count


@final
class HardwareSpecs:
    """Hardware specification summary."""

    def __init__(self) -> None:
        self.pci = get_pci_devices()
        self.usb = get_usb_devices()
        self.gpu_count = count_gpu_devices()

        logger.debug("Hardware specs collected:")
        logger.debug(f"PCI: {self.pci}")
        logger.debug(f"USB: {self.usb}")
        logger.debug(f"GPU count: {self.gpu_count}")
