import logging
import os
import re
from pathlib import Path

from .config_parser import Config, RecursiveConfigEntry, ConfigEntryType
from .file_operations import load_yaml_file
from .hardware_parser import HardwareSpecs
from .state_manager import is_already_installed

logger = logging.getLogger(__name__)


def match_ids(available_ids: list[str], patterns: list[str]) -> list[str]:
    logger.debug("matching available ids in patterns")
    logger.debug("ids:")
    logger.debug(available_ids)
    logger.debug("pattersn:")
    logger.debug(patterns)
    matched: list[str] = []
    for pat in patterns:
        regex = re.compile(pat)
        matched.extend(dev_id for dev_id in available_ids if regex.fullmatch(dev_id))
    logger.debug("matches found:")
    logger.debug(matched)
    return matched


def config_match(hardware_specs: HardwareSpecs, entry: RecursiveConfigEntry) -> bool:
    if entry.pci is not None:
        patterns = entry.pci
        if not match_ids(hardware_specs.pci, patterns):
            return False
    if entry.usb is not None:
        patterns = entry.usb
        if not match_ids(hardware_specs.usb, patterns):
            return False
    if (
        entry.gpu_count_min is not None
        and entry.gpu_count_min > hardware_specs.gpu_count
    ):
        return False

    for not_rule in entry.not_:
        # If at least one not-rule matches, it doesn't match
        if config_match(hardware_specs, not_rule):
            return False

    if entry.any:
        return any(config_match(hardware_specs, rule) for rule in entry.any)

    return True


def get_filtered_config_entries_by_type(
    config: Config, hardware_specs: HardwareSpecs, entry_type: ConfigEntryType
) -> set[str]:
    """Return set of Config entry strings based on PCI/USB matches."""
    filtered_entries: set[str] = set()
    for script, cond in config.root.items():
        if entry_type != cond.type:
            continue
        if not cond or config_match(hardware_specs, cond):
            filtered_entries.add(script)
    return filtered_entries


def get_hardware_config_dependent_scripts(
    hardware_specs: HardwareSpecs, config: Config
) -> set[str]:
    """Return set of hardware dependent scripts based on PCI/USB matches."""
    scripts: set[str] = get_filtered_config_entries_by_type(
        config, hardware_specs, ConfigEntryType.SCRIPT
    )

    logger.debug("scripts:")
    logger.debug(scripts)
    return scripts


def get_hardware_config_dependent_packages(
    hardware_specs: HardwareSpecs, config: Config
) -> set[str]:
    """Return set of hardware dependent packages based on PCI/USB matches."""
    packages: set[str] = get_filtered_config_entries_by_type(
        config, hardware_specs, ConfigEntryType.PKG
    )

    logger.debug("packages before mapping:")
    logger.debug(packages)

    resolved_packages = resolve_mapping(packages)
    logger.debug("resolved packages:")
    logger.debug(resolved_packages)
    return resolved_packages


def get_os_ids() -> set[str]:
    ids: set[str] = set()
    os_release = Path(os.environ.get("TUXEDO_OS_RELEASE", "/etc/os-release"))
    try:
        with os_release.open() as f:
            for line in f:
                if "=" in line:
                    key, val = line.strip().split("=", 1)
                    key = key.strip()
                    val = val.strip()
                    val = val.strip('"')
                    if key == "ID":
                        ids.add(val)
                    elif key == "ID_LIKE":
                        ids.update(val.split())
    except FileNotFoundError:
        logger.error("No OS-Release file found!")
    return ids


def resolve_mapping(packages: set[str]) -> set[str]:
    os_identifiers = get_os_ids()
    package_map = load_yaml_file("/usr/lib/tuxedo-tomte/package_mapper.yaml")
    logger.debug("package map:")
    logger.debug(package_map)
    packages_final: set[str] = set()

    for package in packages:
        mapped_value = package  # default: package stays unchanged
        if package in package_map:
            # iterate through mapping entries of this package
            for os_key, replacement in package_map[package].items():
                if os_key in os_identifiers:
                    mapped_value = replacement
                    break
        if not is_already_installed(mapped_value, ConfigEntryType.PKG):
            packages_final.add(mapped_value)
        else:
            logger.debug(f"Skipping {mapped_value}")
    logger.debug("final packages:")
    logger.debug(packages_final)
    return packages_final
