"""Package detection for container filesystems.

Detects installed packages by parsing package manager databases
from different Linux distributions.
"""

from __future__ import annotations

import logging
import re
from abc import ABC, abstractmethod
from pathlib import Path
from typing import TYPE_CHECKING

from gaol.diff.exceptions import DiffPackageDetectionError
from gaol.diff.models import PackageInfo

if TYPE_CHECKING:
    from gaol.diff.readers.base import FilesystemReader

logger = logging.getLogger(__name__)


class PackageParser(ABC):
    """Abstract base for package database parsers."""

    @property
    @abstractmethod
    def name(self) -> str:
        """Name of this package manager."""
        pass

    @property
    @abstractmethod
    def database_path(self) -> str:
        """Path to package database within container."""
        pass

    @abstractmethod
    def parse(self, content: bytes) -> list[PackageInfo]:
        """Parse package database content.

        Args:
            content: Raw database file content

        Returns:
            List of PackageInfo objects
        """
        pass


class DpkgParser(PackageParser):
    """Parse Debian/Ubuntu dpkg status file.

    Database at: /var/lib/dpkg/status
    """

    @property
    def name(self) -> str:
        return "dpkg"

    @property
    def database_path(self) -> str:
        return "/var/lib/dpkg/status"

    def parse(self, content: bytes) -> list[PackageInfo]:
        """Parse dpkg status file.

        Format is RFC822-like with Package:, Version:, Status: fields.
        """
        packages = []
        text = content.decode("utf-8", errors="replace")

        # Split into package blocks
        blocks = text.split("\n\n")

        for block in blocks:
            if not block.strip():
                continue

            pkg_name = None
            version = None
            status = None
            arch = None
            description = None

            for line in block.split("\n"):
                if line.startswith("Package: "):
                    pkg_name = line[9:].strip()
                elif line.startswith("Version: "):
                    version = line[9:].strip()
                elif line.startswith("Status: "):
                    status = line[8:].strip()
                elif line.startswith("Architecture: "):
                    arch = line[14:].strip()
                elif line.startswith("Description: "):
                    description = line[13:].strip()

            # Only include installed packages
            if pkg_name and version and status:
                # Status is like "install ok installed"
                if "installed" in status.lower():
                    packages.append(
                        PackageInfo(
                            name=pkg_name,
                            version=version,
                            architecture=arch,
                            source="dpkg",
                        )
                    )

        return packages


class RpmParser(PackageParser):
    """Parse RPM package database.

    Uses rpm command output format since the database is binary.
    This parser expects pre-extracted package list content.
    """

    @property
    def name(self) -> str:
        return "rpm"

    @property
    def database_path(self) -> str:
        return "/var/lib/rpm/rpmdb.sqlite"

    def parse(self, content: bytes) -> list[PackageInfo]:
        """Parse rpm query output.

        Expected format: name|version|arch
        One package per line.
        """
        packages = []
        text = content.decode("utf-8", errors="replace")

        for line in text.strip().split("\n"):
            if not line.strip():
                continue

            parts = line.split("|")
            if len(parts) >= 2:
                pkg_name = parts[0].strip()
                version = parts[1].strip()
                arch = parts[2].strip() if len(parts) > 2 else None

                packages.append(
                    PackageInfo(
                        name=pkg_name,
                        version=version,
                        architecture=arch,
                        source="rpm",
                    )
                )

        return packages


class ApkParser(PackageParser):
    """Parse Alpine APK installed database.

    Database at: /lib/apk/db/installed
    """

    @property
    def name(self) -> str:
        return "apk"

    @property
    def database_path(self) -> str:
        return "/lib/apk/db/installed"

    def parse(self, content: bytes) -> list[PackageInfo]:
        """Parse APK installed database.

        Format uses single-letter prefixes:
        P: package name
        V: version
        A: architecture
        T: description
        """
        packages = []
        text = content.decode("utf-8", errors="replace")

        # Split into package blocks (separated by blank lines)
        blocks = re.split(r"\n\n+", text)

        for block in blocks:
            if not block.strip():
                continue

            pkg_name = None
            version = None
            arch = None
            description = None

            for line in block.split("\n"):
                if line.startswith("P:"):
                    pkg_name = line[2:].strip()
                elif line.startswith("V:"):
                    version = line[2:].strip()
                elif line.startswith("A:"):
                    arch = line[2:].strip()
                elif line.startswith("T:"):
                    description = line[2:].strip()

            if pkg_name and version:
                packages.append(
                    PackageInfo(
                        name=pkg_name,
                        version=version,
                        architecture=arch,
                        source="apk",
                    )
                )

        return packages


class PacmanParser(PackageParser):
    """Parse Arch Linux pacman database.

    Database at: /var/lib/pacman/local/
    Each package is a directory with a 'desc' file.
    """

    @property
    def name(self) -> str:
        return "pacman"

    @property
    def database_path(self) -> str:
        return "/var/lib/pacman/local"

    def parse(self, content: bytes) -> list[PackageInfo]:
        """Parse pacman desc file content.

        Format uses %SECTION% markers:
        %NAME%
        package-name

        %VERSION%
        1.2.3-1

        %ARCH%
        x86_64
        """
        packages = []
        text = content.decode("utf-8", errors="replace")

        pkg_name = None
        version = None
        arch = None
        description = None

        current_section = None
        for line in text.split("\n"):
            line = line.strip()

            if line.startswith("%") and line.endswith("%"):
                current_section = line[1:-1]
                continue

            if not line:
                continue

            if current_section == "NAME":
                pkg_name = line
            elif current_section == "VERSION":
                version = line
            elif current_section == "ARCH":
                arch = line
            elif current_section == "DESC":
                description = line

        if pkg_name and version:
            packages.append(
                PackageInfo(
                    name=pkg_name,
                    version=version,
                    architecture=arch,
                    source="pacman",
                )
            )

        return packages


class PackageDetector:
    """Detect and list packages from container filesystems.

    Automatically detects which package managers are present and
    parses their databases.
    """

    # Package parsers in order of preference
    PARSERS: list[type[PackageParser]] = [
        DpkgParser,
        ApkParser,
        PacmanParser,
    ]

    def __init__(self, reader: FilesystemReader) -> None:
        """Initialize detector with filesystem reader.

        Args:
            reader: FilesystemReader to access container files
        """
        self.reader = reader

    def detect_packages(
        self,
        container_id: str,
    ) -> list[PackageInfo]:
        """Detect installed packages in a container.

        Tries each package manager in order and returns packages
        from the first one found.

        Args:
            container_id: Container identifier

        Returns:
            List of detected packages
        """
        for parser_class in self.PARSERS:
            parser = parser_class()

            # Check if this package manager's database exists
            content = self.reader.read_file_content(
                container_id,
                parser.database_path,
                max_size=50 * 1024 * 1024,  # 50MB for large package lists
            )

            if content:
                try:
                    packages = parser.parse(content)
                    if packages:
                        logger.debug(
                            f"Detected {len(packages)} packages via {parser.name}"
                        )
                        return packages
                except Exception as e:
                    logger.warning(f"Failed to parse {parser.name} database: {e}")
                    continue

        # No packages detected (might be a minimal container)
        logger.debug("No package database found")
        return []

    def detect_packages_from_snapshot(
        self,
        container_name: str,
        snapshot_name: str,
    ) -> list[PackageInfo]:
        """Detect packages from a snapshot.

        Args:
            container_name: Container name
            snapshot_name: Snapshot name

        Returns:
            List of detected packages
        """
        for parser_class in self.PARSERS:
            parser = parser_class()

            # For snapshots, we need to use a different approach
            # The reader should handle snapshot file access
            content = self._read_snapshot_file(
                container_name,
                snapshot_name,
                parser.database_path,
            )

            if content:
                try:
                    packages = parser.parse(content)
                    if packages:
                        return packages
                except Exception as e:
                    logger.warning(f"Failed to parse {parser.name} database: {e}")
                    continue

        return []

    def _read_snapshot_file(
        self,
        container_name: str,
        snapshot_name: str,
        path: str,
    ) -> bytes | None:
        """Read a file from a snapshot.

        This is a helper that handles runtime-specific snapshot access.
        """
        # For nspawn, snapshots are directories we can read directly
        if self.reader.runtime_name == "nspawn":
            from gaol.diff.readers.nspawn import MACHINES_DIR

            snapshot_path = (
                MACHINES_DIR
                / f".gaol-snapshots-{container_name}"
                / snapshot_name
                / path.lstrip("/")
            )

            if snapshot_path.exists():
                try:
                    return snapshot_path.read_bytes()
                except Exception:
                    return None

        # For Docker, we'd need to create a temp container
        # This is handled by DockerFilesystemReader.read_snapshot()
        return None


def detect_distro_from_packages(packages: list[PackageInfo]) -> str | None:
    """Try to detect the distro from package names.

    Args:
        packages: List of packages

    Returns:
        Detected distro name or None
    """
    pkg_names = {p.name for p in packages}

    # Debian/Ubuntu indicators
    if "apt" in pkg_names or "dpkg" in pkg_names:
        if "ubuntu-minimal" in pkg_names:
            return "ubuntu"
        return "debian"

    # Alpine indicators
    if "apk-tools" in pkg_names or "alpine-baselayout" in pkg_names:
        return "alpine"

    # Arch indicators
    if "pacman" in pkg_names or "archlinux-keyring" in pkg_names:
        return "arch"

    # Fedora/RHEL indicators
    if "dnf" in pkg_names or "fedora-release" in pkg_names:
        return "fedora"
    if "yum" in pkg_names or "centos-release" in pkg_names:
        return "centos"

    return None
