from __future__ import annotations

from collections import Counter, defaultdict

from .models import AnomalyFinding, LogEvent, Recommendation
from .threat_intel import is_public_ip


def _severity(score: int) -> str:
    if score >= 80:
        return "critical"
    if score >= 60:
        return "high"
    if score >= 35:
        return "medium"
    return "low"


def _top_values(events: list[LogEvent], field: str, limit: int = 5) -> list[str]:
    counter: Counter[str] = Counter()
    for event in events:
        value = event.fields.get(field)
        if value:
            counter[value] += 1
    return [value for value, _ in counter.most_common(limit)]


def _top_policy_values(events: list[LogEvent], limit: int = 5) -> list[str]:
    return [value for value in _top_values(events, "policyid", limit=limit + 1) if value != "0"][:limit]


def _events_by_src(events: list[LogEvent]) -> dict[str, list[LogEvent]]:
    grouped: dict[str, list[LogEvent]] = defaultdict(list)
    for event in events:
        if event.src_ip:
            grouped[event.src_ip].append(event)
    return grouped


def build_recommendations(
    events: list[LogEvent],
    anomalies: list[AnomalyFinding],
    reputation: dict[str, dict[str, object]] | None = None,
) -> list[Recommendation]:
    reputation = reputation or {}
    grouped = _events_by_src(events)
    recommendations: list[Recommendation] = []

    for anomaly in anomalies:
        src_events = grouped.get(anomaly.subject, [])
        if not src_events:
            continue

        policy_ids = _top_policy_values(src_events)
        services = _top_values(src_events, "service")
        dst_ips = [event.dst_ip for event in src_events if event.dst_ip]
        public_dst = [ip for ip in _top_values(src_events, "dstip", limit=10) if is_public_ip(ip)]
        bad_reputation = [
            f"{ip}:score={intel.get('score')} status={intel.get('status')}"
            for ip, intel in reputation.items()
            if int(intel.get("score", 0) or 0) >= 50 and (ip == anomaly.subject or ip in dst_ips)
        ]

        score = anomaly.score
        reasons = list(anomaly.reasons)
        if bad_reputation:
            score = min(100, score + 20)
            reasons.append(f"threat intelligence hit ({'; '.join(bad_reputation[:3])})")

        if anomaly.evidence.get("implicit_deny_events", 0) and not policy_ids:
            title = "Implicit deny/drop traffic observed"
            action = (
                "FortiGate policyid=0 is the implicit deny/drop path, not an editable firewall policy. "
                "If this traffic is expected, create a narrow explicit allow policy above the deny using the observed "
                "source, destination, and service. If it is not expected, keep the deny and investigate or reduce noisy logging."
            )
        elif is_public_ip(anomaly.subject) and anomaly.score >= 60:
            title = "Quarantine or block suspicious public source"
            action = (
                "Inspect the matching FortiGate logs and policy IDs, then quarantine the source IP "
                "temporarily if the traffic is unsolicited or UTM-confirmed. Convert to a permanent "
                "address object/block only after reputation and business impact are verified."
            )
        elif public_dst and bad_reputation:
            title = "Investigate risky destination reputation"
            action = (
                "Inspect the affected internal client, DNS history, and policy path. Consider blocking "
                "the destination with an address object or ISDB/category control if reputation remains malicious."
            )
        elif anomaly.evidence.get("distinct_destinations", 0) >= 10:
            title = "Investigate scan-like or fan-out traffic"
            action = (
                "Inspect the source host and the matching policies. If this is not expected discovery or monitoring, "
                "limit allowed destinations/services and add IPS/application control on the policy."
            )
        elif anomaly.evidence.get("distinct_services", 0) >= 8:
            title = "Restrict broad service usage"
            action = (
                "Review the matching policy services. Replace broad service objects such as ALL with the observed "
                "business-required services only."
            )
        elif anomaly.evidence.get("utm_events", 0):
            title = "Review UTM-triggering traffic"
            action = (
                "Inspect the IPS/AV/WebFilter event details and policy. Keep or enable UTM profiles on this policy, "
                "and tighten source/destination scope if the traffic is not expected."
            )
        else:
            title = "Review traffic anomaly"
            action = "Inspect the related policy and host behavior before changing enforcement."

        recommendations.append(
            Recommendation(
                subject=anomaly.subject,
                score=min(100, score),
                severity=_severity(min(100, score)),
                title=title,
                recommendation=action,
                reasons=reasons,
                related_policy_ids=policy_ids,
                related_services=services,
            )
        )

    return sorted(recommendations, key=lambda item: item.score, reverse=True)
