import sqlite3
from urllib.parse import parse_qs

from fluidattacks_core.semver.match_versions import match_vulnerable_versions

from labels.advisories.database import BaseDatabase
from labels.advisories.match_fixes import match_fixed_versions_new
from labels.advisories.utils import create_advisory_from_record
from labels.model.advisories import Advisory, AdvisoryRecord
from labels.model.ecosystem_data.alpine import ApkDBEntry
from labels.model.ecosystem_data.arch import AlpmDBEntry
from labels.model.ecosystem_data.debian import DpkgDBEntry
from labels.model.ecosystem_data.redhat import RpmDBEntry
from labels.model.package import Ecosystem, Package


class ImagesDatabase(BaseDatabase):
    def __init__(self) -> None:
        super().__init__(db_name="skims_sca_advisories_for_images.db")


DATABASE = ImagesDatabase()


def _normalize_platform_version_for_query(platform_version: str) -> str:
    return platform_version.split(".", maxsplit=1)[0]


def fetch_advisory_from_database(
    cursor: sqlite3.Cursor,
    ecosystem: str,
    platform_version: str,
    package_name: str,
) -> list[AdvisoryRecord]:
    normalized_platform_version = (
        _normalize_platform_version_for_query(platform_version)
        if ecosystem == "rpm"
        else platform_version
    )

    cursor.execute(
        """
        SELECT
            adv_id,
            source,
            vulnerable_version,
            severity_level,
            severity_v4,
            epss,
            details,
            percentile,
            cwe_ids,
            cve_finding,
            auto_approve,
            fixed_versions,
            kev_catalog,
            platform_version
        FROM advisories
        WHERE ecosystem = ? AND platform_version = ? AND package_name = ?;
        """,
        (ecosystem, normalized_platform_version, package_name),
    )
    records: list[AdvisoryRecord] = cursor.fetchall()
    return records


def get_images_package_advisories(
    ecosystem: str,
    package_name: str,
    version: str,
    platform_version: str,
    upstream_package: str | None = None,
) -> list[Advisory]:
    connection = DATABASE.get_connection()
    cursor = connection.cursor()

    return [
        adv
        for record in fetch_advisory_from_database(
            cursor,
            ecosystem,
            platform_version,
            package_name,
        )
        if (
            adv := create_advisory_from_record(
                record, ecosystem, package_name, version, upstream_package
            )
        )
    ]


def get_images_vulnerabilities(
    ecosystem: str,
    product: str,
    version: str,
    platform_version: str | None,
    upstream_info: tuple[str | None, str | None] | None = None,
) -> list[Advisory]:
    advisories_list = []
    if (
        product
        and version
        and platform_version
        and (
            advisories := get_images_package_advisories(
                ecosystem,
                product.lower(),
                version,
                platform_version,
            )
        )
    ):
        advisories_list.extend(
            [
                match_fixed_versions_new(version.lower(), advisor, None, [], advisories)
                for advisor in advisories
                if match_vulnerable_versions(version.lower(), advisor.vulnerable_version)
            ]
        )
    upstream_package, upstream_version = upstream_info or (None, None)
    if (
        upstream_package
        and upstream_version
        and platform_version
        and (
            origin_advisories := get_images_package_advisories(
                ecosystem,
                upstream_package.lower(),
                upstream_version,
                platform_version,
                upstream_package,
            )
        )
    ):
        advisories_list.extend(
            [
                match_fixed_versions_new(version.lower(), advisor, None, [], origin_advisories)
                for advisor in origin_advisories
                if match_vulnerable_versions(version.lower(), advisor.vulnerable_version)
            ]
        )

    return advisories_list


def _get_distro_params(distro_param: str | None) -> tuple[str | None, str | None]:
    if distro_param is None:
        return None, None

    parts = distro_param.rsplit("-", 1)

    if len(parts) == 2:
        return parts[0], parts[1]

    return parts[0], None


def extract_distro_info(pkg_str: str) -> tuple[str | None, str | None, str | None]:
    parts = pkg_str.split("?", 1)
    if len(parts) != 2:
        return None, None, None

    query = parts[1]
    params = parse_qs(query)
    distro_id, distro_version = _get_distro_params(params.get("distro", [None])[0])
    arch = params.get("arch", [None])[0]
    return distro_id, distro_version, arch


def _get_upstream_info(package: Package) -> tuple[str | None, str | None]:
    if (
        package.ecosystem == Ecosystem.ALPINE
        and isinstance(package.ecosystem_data, ApkDBEntry)
        and package.ecosystem_data
        and package.ecosystem_data.origin_package
    ):
        return package.ecosystem_data.origin_package, package.version

    if (
        package.ecosystem == Ecosystem.DEBIAN
        and isinstance(package.ecosystem_data, DpkgDBEntry)
        and package.ecosystem_data
        and package.ecosystem_data.source
    ):
        return (
            package.ecosystem_data.source,
            package.ecosystem_data.source_version or package.version,
        )

    if (
        package.ecosystem == Ecosystem.ALPM
        and isinstance(package.ecosystem_data, AlpmDBEntry)
        and package.ecosystem_data
        and package.ecosystem_data.base_package
    ):
        return package.ecosystem_data.base_package, package.version

    if (
        package.ecosystem == Ecosystem.RPM
        and isinstance(package.ecosystem_data, RpmDBEntry)
        and package.ecosystem_data
        and package.ecosystem_data.source_rpm
    ):
        return _extract_rpm_upstream_info(package.ecosystem_data.source_rpm)

    return None, None


def _extract_rpm_upstream_info(source_rpm: str) -> tuple[str | None, str | None]:
    """Extract upstream package name and version from RPM source package.

    RPM format: NAME-VERSION-RELEASE[.ARCH].src.rpm or NAME-VERSION-RELEASE[.ARCH].nosrc.rpm
    Example: pam-1.5.1-25.el9_6.x86_64.src.rpm -> (pam, 1.5.1-25.el9_6)
    Example: pam-1.5.1-25.el9_6.src.rpm -> (pam, 1.5.1-25.el9_6)
    Example: foo-1-1.el7.nosrc.rpm -> (foo, 1-1.el7.nosrc)
    """
    # Remove source RPM suffixes
    if source_rpm.endswith(".nosrc.rpm"):
        source_rpm = source_rpm[:-10]  # Remove .nosrc.rpm
    elif source_rpm.endswith(".src.rpm"):
        source_rpm = source_rpm[:-8]  # Remove .src.rpm

    # Known RPM architectures - only remove if last component matches these
    known_architectures = {
        "x86_64",
        "i386",
        "i486",
        "i586",
        "i686",
        "aarch64",
        "armv7hl",
        "armv7l",
        "armv6hl",
        "ppc64le",
        "ppc64",
        "s390x",
        "noarch",
    }

    # Remove architecture only if present and recognized
    if "." in source_rpm:
        possible_arch = source_rpm.rsplit(".", 1)[1]
        if possible_arch in known_architectures:
            source_rpm = source_rpm.rsplit(".", 1)[0]

    # Now split by hyphens: NAME-VERSION-RELEASE
    parts = source_rpm.split("-")
    if len(parts) >= 3:
        name = "-".join(parts[:-2])
        version = parts[-2]
        release = parts[-1]
        full_version = f"{version}-{release}"
        return name, full_version

    return None, None


def _get_rhel_distro_info(package: Package) -> tuple[str | None, str | None]:
    """Extract RHEL distribution information from RPM package.

    Returns:
        tuple: (distro_id, distro_version) where:
        - distro_id: "rpm" for RHEL packages
        - distro_version: "rhel9.6" for RHEL 9.6, "rhel8" for RHEL 8, etc.

    """
    if not (
        package.ecosystem == Ecosystem.RPM
        and isinstance(package.ecosystem_data, RpmDBEntry)
        and package.ecosystem_data
        and package.ecosystem_data.release
    ):
        return None, None

    release = package.ecosystem_data.release

    # Look for RHEL pattern: .elX or .elX_Y
    if ".el" not in release:
        return None, None

    # Extract the part after .el (e.g., "9_6" from "63.el9_6")
    el_part = release.split(".el")[-1]
    if not el_part or not el_part[0].isdigit():
        return None, None
    # Parse RHEL version
    if "_" in el_part:
        # Full version: "9_6.1" -> "rhel9.6.1"
        parts = el_part.split("_")
        major, minor = parts[0], parts[1]
        return "rpm", f"rhel{major}.{minor}"

    # Major version only: "9" -> "rhel9"
    return "rpm", f"rhel{el_part}"


def _normalize_rpm_version(version: str) -> str:
    """Normalize RPM version by adding epoch 0: if not present."""
    # Check if version already has an epoch (contains ':')
    if ":" in version:
        return version

    # Add epoch 0: if not present
    return f"0:{version}"


def _get_upstream_info_if_different(
    package: Package, upstream_package: str | None, upstream_version: str | None
) -> tuple[str | None, str | None] | None:
    if (
        upstream_package
        and upstream_version
        and (upstream_package != package.name or upstream_version != package.version)
    ):
        return (upstream_package, upstream_version)
    return None


def update_image_advisories(package: Package) -> list[Advisory]:
    distro_id = None
    distro_version = None
    if package.ecosystem in [Ecosystem.DEBIAN, Ecosystem.ALPM, Ecosystem.ALPINE]:
        distro_id, distro_version, _ = extract_distro_info(package.p_url)
        distro_version = (
            "v" + ".".join(str(distro_version).split(".")[0:2])
            if package.ecosystem == Ecosystem.ALPINE
            else str(distro_version)
        )

    if package.ecosystem == Ecosystem.RPM:
        distro_id, distro_version = _get_rhel_distro_info(package)

    upstream_package, upstream_version = _get_upstream_info(package)
    upstream_info = _get_upstream_info_if_different(package, upstream_package, upstream_version)

    normalized_version = package.version
    if package.ecosystem == Ecosystem.RPM:
        normalized_version = _normalize_rpm_version(package.version)

    return get_images_vulnerabilities(
        str(distro_id),
        package.name,
        normalized_version,
        distro_version,
        upstream_info,
    )
