"""Proofline — validates indicators, measures freshness, scores confidence.

Workers: IOCChecker (are the observables well-formed?), FreshnessChecker (how
old is the signal?), ConfidenceScorer (Admiralty source-reliability + an
overall confidence level). Fills case.confidence; runs after Scoutline.
"""

from __future__ import annotations

import re
from datetime import datetime, timezone

from psyc import log
from psyc.models import Case


_log = log.get(__name__)

_CVE_RE = re.compile(r"^CVE-\d{4}-\d{4,}$", re.IGNORECASE)
_IPV4_RE = re.compile(r"^\d{1,3}(\.\d{1,3}){3}$")
_DOMAIN_RE = re.compile(r"^[a-z0-9-]+(\.[a-z0-9-]+)+$", re.IGNORECASE)
_SHA_RE = re.compile(r"^[a-fA-F0-9]{32,64}$")

# feed -> (Admiralty source reliability A-F, information credibility 1-6)
_FEED_RELIABILITY = {
    "cisa-kev": ("A", "1"),       # government catalog, confirmed exploited
    "urlhaus": ("B", "2"),        # established CTI source, confirmed malware
    "feodo": ("B", "2"),          # established CTI source, confirmed C2
    "threatfox": ("B", "2"),      # abuse.ch CTI source
    "malware-bazaar": ("B", "2"), # abuse.ch CTI source, confirmed sample
    "otx": ("C", "3"),            # community-driven, varying quality
}


def prove(case: Case) -> Case:
    _check_iocs(case)
    _check_freshness(case)
    _score_confidence(case)
    _log.info(
        "proof.scored",
        case_id=case.case_id,
        level=case.confidence.level,
        reliability=case.confidence.source_reliability,
        freshness=case.confidence.freshness,
        iocs_valid=case.confidence.iocs_valid,
    )
    return case


def _ipv4_ok(value: str) -> bool:
    if not _IPV4_RE.match(value):
        return False
    return all(0 <= int(part) <= 255 for part in value.split("."))


def _check_iocs(case: Case) -> None:
    obs = case.observables
    valid = True
    for ip in obs.ips:
        if not _ipv4_ok(ip):
            valid = False
    for cve in obs.cves:
        if not _CVE_RE.match(cve):
            valid = False
    for h in obs.hashes:
        if not _SHA_RE.match(h):
            valid = False
    for d in obs.domains:
        # an IP used as a host is a valid observable even though it is not a domain
        if not (_DOMAIN_RE.match(d) or _ipv4_ok(d)):
            valid = False
    case.confidence.iocs_valid = valid


def _check_freshness(case: Case) -> None:
    age_days = (datetime.now(timezone.utc) - case.observed_at).days
    if age_days <= 2:
        case.confidence.freshness = "new"
    elif age_days <= 14:
        case.confidence.freshness = "recent"
    elif age_days <= 90:
        case.confidence.freshness = "stale"
    else:
        case.confidence.freshness = "resurfaced"


def _score_confidence(case: Case) -> None:
    feed = case.source_metadata.get("feed", "")
    reliability, credibility = _FEED_RELIABILITY.get(feed, ("C", "3"))
    case.confidence.source_reliability = reliability
    case.confidence.information_credibility = credibility

    # level: start from source reliability, then dock for staleness / bad IOCs
    level = "high" if reliability == "A" else "medium"
    if case.confidence.freshness in ("stale", "resurfaced"):
        level = "medium" if level == "high" else "low"
    if not case.confidence.iocs_valid:
        level = "low"
    case.confidence.level = level
