"""Pydantic models for file-based fail2ban configuration management.

Covers jail config files (``jail.d/``), filter definitions (``filter.d/``),
and action definitions (``action.d/``).
"""

from pydantic import Field, field_validator

from app.models.response import BanGuiBaseModel
from app.utils.constants import FAIL2BAN_RESERVED_JAIL_NAMES

# ---------------------------------------------------------------------------
# Jail config file models (Task 4a)
# ---------------------------------------------------------------------------

class JailConfigFile(BanGuiBaseModel):
    """Metadata for a single jail configuration file in ``jail.d/``."""

    name: str = Field(..., description="Jail name (file stem, e.g. ``sshd``).")
    filename: str = Field(..., description="Actual filename (e.g. ``sshd.conf``).")
    enabled: bool = Field(
        ...,
        description=(
            "Whether the jail is enabled.  Derived from the ``enabled`` key "
            "inside the file; defaults to ``true`` when the key is absent."
        ),
    )

class JailConfigFilesResponse(BanGuiBaseModel):
    """Response for ``GET /api/config/jail-files``."""

    files: list[JailConfigFile] = Field(default_factory=list)
    total: int = Field(..., ge=0)

class JailConfigFileContent(BanGuiBaseModel):
    """Single jail config file with its raw content."""

    name: str = Field(..., description="Jail name (file stem).")
    filename: str = Field(..., description="Actual filename.")
    enabled: bool = Field(..., description="Whether the jail is enabled.")
    content: str = Field(..., description="Raw file content.")

class JailConfigFileEnabledUpdate(BanGuiBaseModel):
    """Payload for ``PUT /api/config/jail-files/{filename}/enabled``."""

    enabled: bool = Field(..., description="New enabled state for this jail.")

# ---------------------------------------------------------------------------
# Generic conf-file entry (shared by filter.d and action.d)
# ---------------------------------------------------------------------------

class ConfFileEntry(BanGuiBaseModel):
    """Metadata for a single ``.conf`` or ``.local`` file."""

    name: str = Field(..., description="Base name without extension (e.g. ``sshd``).")
    filename: str = Field(..., description="Actual filename (e.g. ``sshd.conf``).")

class ConfFilesResponse(BanGuiBaseModel):
    """Response for list endpoints (``GET /api/config/filters`` and ``GET /api/config/actions``)."""

    files: list[ConfFileEntry] = Field(default_factory=list)
    total: int = Field(..., ge=0)

class ConfFileContent(BanGuiBaseModel):
    """A conf file with its raw text content."""

    name: str = Field(..., description="Base name without extension.")
    filename: str = Field(..., description="Actual filename.")
    content: str = Field(..., description="Raw file content.")

class ConfFileUpdateRequest(BanGuiBaseModel):
    """Payload for ``PUT /api/config/filters/{name}`` and ``PUT /api/config/actions/{name}``."""

    content: str = Field(..., description="New raw file content (must not exceed 512 KB).")

class ConfFileCreateRequest(BanGuiBaseModel):
    """Payload for ``POST /api/config/filters`` and ``POST /api/config/actions``."""

    name: str = Field(
        ...,
        description="New file base name (without extension).  Must contain only "
        "alphanumeric characters, hyphens, underscores, and dots.",
    )
    content: str = Field(..., description="Initial raw file content (must not exceed 512 KB).")

    @field_validator("name", mode="after")
    @classmethod
    def _reject_reserved_jail_name(cls, v: str) -> str:
        """Reject fail2ban reserved jail names."""
        if v in FAIL2BAN_RESERVED_JAIL_NAMES:

            valid_names = ", ".join(sorted(FAIL2BAN_RESERVED_JAIL_NAMES))
            raise ValueError(
                f"Jail name {v!r} is reserved by fail2ban ({valid_names})."
            )
        return v
