from __future__ import annotations

import json
from dataclasses import dataclass
from pathlib import Path
from typing import List, Optional, Tuple

from .models import FindingDetail, PluginMetadata, Severity


@dataclass
class Advisory:
    plugin: str
    vulnerable_versions: str
    cve: Optional[str]
    severity: str
    title: str
    reference: Optional[str]
    fixed_in: Optional[str] = None


def load_vuln_db(path: Path) -> List[Advisory]:
    """
    Load a lightweight JSON database with entries like:
    [{"plugin": "contact-form-7", "vulnerable_versions": "<= 5.8", "cve": "CVE-XXXX", "severity": "high", "title": "...", "reference": "https://..."}]
    """
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return []
    advisories: List[Advisory] = []
    if not isinstance(data, list):
        return []
    for entry in data:
        if not isinstance(entry, dict):
            continue
        advisories.append(
            Advisory(
                plugin=entry.get("plugin", ""),
                vulnerable_versions=entry.get("vulnerable_versions", ""),
                cve=entry.get("cve"),
                severity=entry.get("severity", "medium"),
                title=entry.get("title", ""),
                reference=entry.get("reference"),
                fixed_in=entry.get("fixed_in"),
            )
        )
    return advisories


def _parse_constraint(expr: str) -> List[Tuple[str, str]]:
    """
    Parse simple semver-like constraints, e.g. "<= 5.8", ">=1.0", "<2.0".
    """
    constraints: List[Tuple[str, str]] = []
    for part in expr.split(","):
        part = part.strip()
        if not part:
            continue
        if part.startswith("<="):
            constraints.append(("<=", part[2:].strip()))
        elif part.startswith(">="):
            constraints.append((">=", part[2:].strip()))
        elif part.startswith("<"):
            constraints.append(("<", part[1:].strip()))
        elif part.startswith(">"):
            constraints.append((">", part[1:].strip()))
        elif part.startswith("=="):
            constraints.append(("==", part[2:].strip()))
        elif part.startswith("="):
            constraints.append(("==", part[1:].strip()))
        else:
            constraints.append(("==", part))
    return constraints


def _version_tuple(version: str) -> Tuple[int, ...]:
    parts = []
    for token in version.split("."):
        try:
            parts.append(int(token))
        except ValueError:
            parts.append(0)
    return tuple(parts)


def _satisfies(version: str, expr: str) -> bool:
    if not version or not expr:
        return True
    v = _version_tuple(version)
    for op, target in _parse_constraint(expr):
        t = _version_tuple(target)
        if op == "<=" and not (v <= t):
            return False
        if op == ">=" and not (v >= t):
            return False
        if op == "<" and not (v < t):
            return False
        if op == ">" and not (v > t):
            return False
        if op == "==" and not (v == t):
            return False
    return True


def match_vulnerabilities(meta: PluginMetadata, advisories: List[Advisory]) -> List[FindingDetail]:
    findings: List[FindingDetail] = []
    plugin_slug = (meta.plugin_uri or meta.name or "").lower()
    version = meta.version or ""
    if not plugin_slug:
        return findings
    for adv in advisories:
        if adv.plugin.lower() not in plugin_slug:
            continue
        if adv.vulnerable_versions and version and not _satisfies(version, adv.vulnerable_versions):
            continue
        sev = adv.severity.lower()
        severity = (
            Severity.CRITICAL
            if sev in {"critical"}
            else Severity.HIGH
            if sev in {"high"}
            else Severity.MEDIUM
        )
        findings.append(
            FindingDetail(
                category="known-vuln",
                message=f"{adv.title} (affected: {adv.vulnerable_versions})",
                severity=severity,
                evidence=f"CVE: {adv.cve}" if adv.cve else None,
                remediation=f"Update to {adv.fixed_in or 'a patched version'} or apply vendor mitigation.",
                request_example=adv.reference,
            )
        )
    return findings
