"""Pydantic models for the security module."""

from __future__ import annotations

import re
from datetime import datetime
from enum import Enum
from typing import Annotated

from pydantic import BaseModel, Field, field_validator


class FalcoPriority(str, Enum):
    """Falco event priority levels."""

    EMERGENCY = "Emergency"
    ALERT = "Alert"
    CRITICAL = "Critical"
    ERROR = "Error"
    WARNING = "Warning"
    NOTICE = "Notice"
    INFORMATIONAL = "Informational"
    DEBUG = "Debug"

    @classmethod
    def from_string(cls, value: str) -> FalcoPriority:
        """Convert string to priority, case-insensitive."""
        value_lower = value.lower()
        for priority in cls:
            if priority.value.lower() == value_lower:
                return priority
        raise ValueError(f"Unknown priority: {value}")

    def __lt__(self, other: object) -> bool:
        """Compare priorities (Emergency is highest)."""
        if not isinstance(other, FalcoPriority):
            return NotImplemented
        order = list(FalcoPriority)
        return order.index(self) < order.index(other)

    def __le__(self, other: object) -> bool:
        if not isinstance(other, FalcoPriority):
            return NotImplemented
        return self == other or self < other


class ContainerInfo(BaseModel):
    """Container information from Falco event."""

    id: str | None = None
    name: str | None = None
    image: str | None = None
    image_tag: str | None = None
    image_digest: str | None = None


class SecurityEvent(BaseModel):
    """A security event from Falco."""

    uuid: str
    timestamp: datetime
    rule: str
    priority: FalcoPriority
    output: str
    output_fields: dict[str, str] = Field(default_factory=dict)
    source: str = "syscall"
    container: ContainerInfo | None = None
    hostname: str | None = None
    tags: list[str] = Field(default_factory=list)

    def matches_container(self, name_pattern: str) -> bool:
        """Check if event matches a container name pattern."""
        if self.container is None or self.container.name is None:
            return False
        return bool(re.match(name_pattern, self.container.name))

    def is_at_least_priority(self, min_priority: FalcoPriority) -> bool:
        """Check if event priority is at least the given level."""
        return self.priority <= min_priority


class SecurityRule(BaseModel):
    """A Falco security rule definition."""

    name: Annotated[str, Field(min_length=1, max_length=128, pattern=r"^[a-zA-Z_][a-zA-Z0-9_]*$")]
    description: str = ""
    condition: str
    output: str
    priority: FalcoPriority = FalcoPriority.WARNING
    source: str = "syscall"
    tags: list[str] = Field(default_factory=list)
    enabled: bool = True

    # Gaol-specific metadata
    gaol_managed: bool = True
    version: str = "1.0.0"
    applies_to_containers: list[str] = Field(default_factory=list)

    def to_falco_yaml(self) -> dict:
        """Convert to Falco YAML rule format."""
        rule_dict = {
            "rule": self.name,
            "desc": self.description,
            "condition": self.condition,
            "output": self.output,
            "priority": self.priority.value,
            "source": self.source,
            "tags": self.tags,
            "enabled": self.enabled,
        }
        return rule_dict


class RuleList(BaseModel):
    """Collection of Falco rules with macros and lists."""

    required_engine_version: str = "0.26.0"
    macros: dict[str, str] = Field(default_factory=dict)
    lists: dict[str, list[str]] = Field(default_factory=dict)
    rules: list[SecurityRule] = Field(default_factory=list)

    def to_falco_yaml(self) -> list[dict]:
        """Convert to Falco YAML format (list of items)."""
        items: list[dict] = []

        # Add required engine version
        items.append({"required_engine_version": self.required_engine_version})

        # Add macros
        for name, condition in self.macros.items():
            items.append({"macro": name, "condition": condition})

        # Add lists
        for name, values in self.lists.items():
            items.append({"list": name, "items": values})

        # Add rules
        for rule in self.rules:
            items.append(rule.to_falco_yaml())

        return items


# Seccomp models


class SeccompAction(str, Enum):
    """Seccomp actions."""

    SCMP_ACT_ALLOW = "SCMP_ACT_ALLOW"
    SCMP_ACT_ERRNO = "SCMP_ACT_ERRNO"
    SCMP_ACT_KILL = "SCMP_ACT_KILL"
    SCMP_ACT_KILL_PROCESS = "SCMP_ACT_KILL_PROCESS"
    SCMP_ACT_LOG = "SCMP_ACT_LOG"
    SCMP_ACT_TRACE = "SCMP_ACT_TRACE"
    SCMP_ACT_TRAP = "SCMP_ACT_TRAP"


class SeccompArch(str, Enum):
    """Seccomp architectures."""

    SCMP_ARCH_X86_64 = "SCMP_ARCH_X86_64"
    SCMP_ARCH_X86 = "SCMP_ARCH_X86"
    SCMP_ARCH_AARCH64 = "SCMP_ARCH_AARCH64"
    SCMP_ARCH_ARM = "SCMP_ARCH_ARM"


class SeccompArgument(BaseModel):
    """A syscall argument filter."""

    index: int
    value: int
    valueTwo: int = 0
    op: str = "SCMP_CMP_EQ"


class SeccompSyscall(BaseModel):
    """A syscall entry in seccomp profile."""

    names: list[str]
    action: SeccompAction
    args: list[SeccompArgument] = Field(default_factory=list)
    comment: str = ""


class SeccompProfile(BaseModel):
    """Seccomp security profile (OCI format)."""

    defaultAction: SeccompAction = SeccompAction.SCMP_ACT_ERRNO
    architectures: list[str] = Field(
        default_factory=lambda: ["SCMP_ARCH_X86_64", "SCMP_ARCH_AARCH64"]
    )
    syscalls: list[SeccompSyscall] = Field(default_factory=list)

    # Metadata (not part of OCI spec)
    name: str = ""
    description: str = ""
    generated_at: datetime | None = None
    generated_from: str | None = None

    def to_oci_json(self) -> dict:
        """Convert to OCI seccomp profile JSON format."""
        return {
            "defaultAction": self.defaultAction.value,
            "architectures": self.architectures,
            "syscalls": [
                {
                    "names": s.names,
                    "action": s.action.value,
                    **({"args": [a.model_dump() for a in s.args]} if s.args else {}),
                }
                for s in self.syscalls
            ],
        }

    def add_syscall(
        self,
        names: list[str],
        action: SeccompAction = SeccompAction.SCMP_ACT_ALLOW,
    ) -> None:
        """Add syscalls to the profile."""
        # Check if we already have an entry with the same action
        for syscall in self.syscalls:
            if syscall.action == action:
                syscall.names.extend(n for n in names if n not in syscall.names)
                return

        self.syscalls.append(SeccompSyscall(names=names, action=action))


# AppArmor models


class AppArmorMode(str, Enum):
    """AppArmor profile modes."""

    ENFORCE = "enforce"
    COMPLAIN = "complain"
    DISABLED = "disabled"


class AppArmorProfile(BaseModel):
    """AppArmor security profile."""

    name: Annotated[str, Field(min_length=1, pattern=r"^[a-zA-Z0-9_.-]+$")]
    mode: AppArmorMode = AppArmorMode.ENFORCE

    # Capabilities
    capabilities_allow: list[str] = Field(default_factory=list)
    capabilities_deny: list[str] = Field(default_factory=list)

    # File access patterns
    file_read: list[str] = Field(default_factory=list)
    file_write: list[str] = Field(default_factory=list)
    file_exec: list[str] = Field(default_factory=list)
    file_deny: list[str] = Field(default_factory=list)

    # Network access
    network_allow: list[str] = Field(default_factory=list)  # tcp, udp, unix, raw

    # Metadata
    description: str = ""
    generated_at: datetime | None = None
    generated_from: str | None = None

    @field_validator("capabilities_allow", "capabilities_deny", mode="before")
    @classmethod
    def lowercase_capabilities(cls, v: list[str]) -> list[str]:
        return [cap.lower() for cap in v]

    def to_apparmor_text(self) -> str:
        """Generate AppArmor profile text."""
        lines = [
            f"# AppArmor profile for {self.name}",
            "# Generated by gaol",
            "",
            f"profile {self.name} flags=(attach_disconnected,mediate_deleted) {{",
        ]

        # Include common abstractions
        lines.append("  #include <abstractions/base>")
        lines.append("")

        # Capabilities
        for cap in self.capabilities_allow:
            lines.append(f"  capability {cap},")
        for cap in self.capabilities_deny:
            lines.append(f"  deny capability {cap},")

        if self.capabilities_allow or self.capabilities_deny:
            lines.append("")

        # Network
        for net in self.network_allow:
            lines.append(f"  network {net},")

        if self.network_allow:
            lines.append("")

        # File access
        for path in self.file_read:
            lines.append(f"  {path} r,")
        for path in self.file_write:
            lines.append(f"  {path} w,")
        for path in self.file_exec:
            lines.append(f"  {path} ix,")
        for path in self.file_deny:
            lines.append(f"  deny {path} rwx,")

        lines.append("}")
        lines.append("")

        return "\n".join(lines)


# Falco status


class FalcoStatus(BaseModel):
    """Status of Falco service."""

    installed: bool = False
    running: bool = False
    version: str | None = None
    grpc_enabled: bool = False
    grpc_socket: str | None = None
    grpc_address: str | None = None
    rules_loaded: int = 0
    uptime_seconds: float | None = None
    driver_type: str | None = None  # modern_ebpf, ebpf, kmod


# Event summary


class EventSummary(BaseModel):
    """Summary of security events."""

    total_events: int = 0
    events_by_priority: dict[str, int] = Field(default_factory=dict)
    events_by_rule: dict[str, int] = Field(default_factory=dict)
    events_by_container: dict[str, int] = Field(default_factory=dict)
    time_range_start: datetime | None = None
    time_range_end: datetime | None = None
