from __future__ import annotations

import ipaddress
import os
from collections import defaultdict
from dataclasses import dataclass
from typing import Iterable
from urllib import error, request
import json
import ssl
from urllib.parse import urlencode

from .logs import event_score, is_utm_event
from .models import BlockCandidate, LogEvent


def parse_allowlist(value: str | None = None) -> list[ipaddress._BaseNetwork]:
    raw = value if value is not None else os.getenv("FGAI_ALLOWLIST", "")
    networks: list[ipaddress._BaseNetwork] = []
    for item in raw.split(","):
        item = item.strip()
        if not item:
            continue
        networks.append(ipaddress.ip_network(item, strict=False))
    return networks


def is_blockable_public_ip(ip: str, allowlist: Iterable[ipaddress._BaseNetwork] = ()) -> bool:
    try:
        address = ipaddress.ip_address(ip)
    except ValueError:
        return False
    if not address.is_global:
        return False
    return not any(address in network for network in allowlist)


def suggest_block_candidates(
    events: Iterable[LogEvent],
    *,
    min_events: int = 3,
    min_score: int = 7,
    allowlist: Iterable[ipaddress._BaseNetwork] = (),
) -> list[BlockCandidate]:
    grouped: dict[str, list[LogEvent]] = defaultdict(list)
    for event in events:
        if not event.src_ip or not is_utm_event(event):
            continue
        if not is_blockable_public_ip(event.src_ip, allowlist):
            continue
        if event_score(event) > 0:
            grouped[event.src_ip].append(event)

    candidates: list[BlockCandidate] = []
    for src_ip, ip_events in grouped.items():
        score = sum(event_score(event) for event in ip_events)
        if len(ip_events) < min_events and score < min_score:
            continue
        reasons = sorted(
            {
                f"{event.subtype or 'utm'}:{event.action or 'observed'}:{event.severity or 'unknown'}"
                for event in ip_events
            }
        )
        candidates.append(BlockCandidate(src_ip, score, reasons, ip_events[-5:]))

    return sorted(candidates, key=lambda candidate: candidate.score, reverse=True)


@dataclass(frozen=True)
class FortiGateClient:
    host: str
    api_token: str
    verify_tls: bool = True

    @classmethod
    def from_env(cls) -> "FortiGateClient":
        host = os.environ["FORTIGATE_HOST"]
        token = os.environ["FORTIGATE_API_TOKEN"]
        verify_tls = os.getenv("FORTIGATE_VERIFY_TLS", "true").lower() not in {"0", "false", "no"}
        return cls(host=host, api_token=token, verify_tls=verify_tls)

    def quarantine_ip(self, src_ip: str, expiry_minutes: int, reason: str) -> str:
        payload = {
            "ip_address": src_ip,
            "expiry": expiry_minutes,
            "source": "fgai-local-agent",
            "reason": reason,
        }
        url = f"https://{self.host}/api/v2/monitor/user/banned/create"
        body = json.dumps(payload).encode("utf-8")
        req = request.Request(
            url,
            data=body,
            method="POST",
            headers={
                "Authorization": f"Bearer {self.api_token}",
                "Content-Type": "application/json",
                "Accept": "application/json",
            },
        )
        context = None if self.verify_tls else ssl._create_unverified_context()
        try:
            with request.urlopen(req, timeout=15, context=context) as response:
                return response.read().decode("utf-8", errors="replace")
        except error.HTTPError as exc:
            detail = exc.read().decode("utf-8", errors="replace")
            raise RuntimeError(f"FortiGate API returned HTTP {exc.code}: {detail}") from exc
        except error.URLError as exc:
            raise RuntimeError(f"Could not reach FortiGate API: {exc.reason}") from exc

    def get_json(self, path: str, params: dict[str, str] | None = None) -> dict[str, object]:
        query = f"?{urlencode(params)}" if params else ""
        url = f"https://{self.host}{path}{query}"
        req = request.Request(
            url,
            method="GET",
            headers={
                "Authorization": f"Bearer {self.api_token}",
                "Accept": "application/json",
            },
        )
        context = None if self.verify_tls else ssl._create_unverified_context()
        try:
            with request.urlopen(req, timeout=15, context=context) as response:
                return json.loads(response.read().decode("utf-8"))
        except error.HTTPError as exc:
            detail = exc.read().decode("utf-8", errors="replace")
            raise RuntimeError(f"FortiGate API returned HTTP {exc.code}: {detail}") from exc
        except error.URLError as exc:
            raise RuntimeError(f"Could not reach FortiGate API: {exc.reason}") from exc
        except json.JSONDecodeError as exc:
            raise RuntimeError("FortiGate API returned invalid JSON") from exc

    def system_status(self) -> dict[str, object]:
        return self.get_json("/api/v2/monitor/system/status")

    def firewall_policies(self) -> dict[str, object]:
        return self.get_json("/api/v2/cmdb/firewall/policy")
