"""
Helpers for turning raw Windows Firewall rule data into clean, structured objects.

Windows stores firewall rules as long, messy strings in the registry. This module
takes those raw fields and converts them into a `FirewallRule` dataclass with
clear types for things like direction (inbound/outbound), action (allow/block),
protocol (TCP/UDP), and which ports the rule applies to.

The enums at the top define the allowed values so the rest of the agent doesn’t
have to deal with inconsistent strings. The normalization functions translate
registry values—like “6” for TCP or “1” for the Domain profile—into readable,
predictable Python values. Port lists are also cleaned up so ranges and
comma‑separated lists become real lists of integers.

The end result is a structured rule object that’s easy for the agent to reason
about, serialize to JSON, or display to the user without worrying about the
quirks of the Windows registry format.
"""


import json
from dataclasses import dataclass
from enum import Enum
from typing import List, Optional


class Direction(Enum):
    INBOUND = "inbound"
    OUTBOUND = "outbound"


class Action(Enum):
    ALLOW = "allow"
    BLOCK = "block"


class Protocol(Enum):
    TCP = "tcp"
    UDP = "udp"
    ANY = "any"
    OTHER = "other"  # fallback


class Profile(Enum):
    DOMAIN = "domain"
    PRIVATE = "private"
    PUBLIC = "public"
    ANY = "any"


@dataclass
class FirewallRule:
    id: str
    name: Optional[str]
    description: Optional[str]
    enabled: bool
    direction: Direction
    action: Action
    protocol: Protocol
    local_ports: List[int]  # always a list of ints
    remote_ports: List[str]  # may include "*" or ranges
    program: Optional[str]
    service: Optional[str]
    profiles: List[Profile]
    source: str  # registry path

    def to_dict(self) -> dict:
        """Convert the rule into a JSON‑ready dictionary."""
        return {
            "id": self.id,
            "name": self.name,
            "description": self.description,
            "enabled": self.enabled,
            "direction": self.direction.value,
            "action": self.action.value,
            "protocol": self.protocol.value,
            "local_ports": self.local_ports,
            "remote_ports": self.remote_ports,
            "program": self.program,
            "service": self.service,
            "profiles": [p.value for p in self.profiles],
            "source": self.source,
        }

    def to_json(self, indent: int = 2) -> str:
        return json.dumps(self.to_dict(), indent=indent)


def rules_to_json(rules: List[FirewallRule], indent: int = 2) -> str:
    return json.dumps([r.to_dict() for r in rules], indent=indent)


def normalize_bool(value: Optional[str]) -> bool:
    if not value:
        return False
    return value.strip().lower() in ("true", "yes", "1")


def normalize_direction(value: Optional[str]) -> Direction:
    if not value:
        return Direction.INBOUND
    v = value.lower()
    return Direction.INBOUND if v.startswith("in") else Direction.OUTBOUND


def normalize_action(value: Optional[str]) -> Action:
    if not value:
        return Action.ALLOW
    return Action.ALLOW if value.lower() == "allow" else Action.BLOCK


def normalize_protocol(value: Optional[str]) -> Protocol:
    if not value:
        return Protocol.ANY

    try:
        num = int(value)
    except ValueError:
        return Protocol.OTHER

    if num == 6:
        return Protocol.TCP
    if num == 17:
        return Protocol.UDP
    if num in (256, 0):
        return Protocol.ANY

    return Protocol.OTHER


def normalize_profiles(value: Optional[str]) -> List[Profile]:
    if not value:
        return [Profile.ANY]

    try:
        mask = int(value)
    except ValueError:
        return [Profile.ANY]

    profiles = []
    if mask & 1:
        profiles.append(Profile.DOMAIN)
    if mask & 2:
        profiles.append(Profile.PRIVATE)
    if mask & 4:
        profiles.append(Profile.PUBLIC)

    return profiles or [Profile.ANY]


def parse_ports(lport: Optional[str]) -> List[int]:
    """Turn a port string like '80,443,1000-1005' into a list of real port numbers, keeping '*' as a wildcard."""

    if not lport:
        return []

    lport = lport.strip()

    if lport == "*" or lport.lower() == "any":
        return ["*"]  # wildcard preserved

    ports = []
    for part in lport.split(","):
        part = part.strip()
        if "-" in part:
            try:
                start, end = part.split("-", 1)
                ports.extend(range(int(start), int(end) + 1))
            except ValueError:
                continue
        else:
            try:
                ports.append(int(part))
            except ValueError:
                continue

    return ports


def normalize_firewall_rule(raw: dict, source: str) -> FirewallRule:
    return FirewallRule(
        id=raw.get("ValueName"),
        name=raw.get("Name"),
        description=raw.get("Desc"),
        enabled=normalize_bool(raw.get("Active")),
        direction=normalize_direction(raw.get("Dir")),
        action=normalize_action(raw.get("Action")),
        protocol=normalize_protocol(raw.get("Protocol")),
        local_ports=parse_ports(raw.get("LPort")),
        remote_ports=[raw.get("RPort")] if raw.get("RPort") else [],
        program=raw.get("App"),
        service=raw.get("Svc"),
        profiles=normalize_profiles(raw.get("Profile")),
        source=source,
    )
