# scm/models/security/security_rules.py

from typing import List, Optional
from uuid import UUID

from pydantic import (
    BaseModel,
    Field,
    field_validator,
    model_validator,
    ConfigDict,
    constr,
)
from enum import Enum


# Enums
class RuleMoveDestination(str, Enum):
    """Enum representing valid destination values for rule movement."""

    TOP = "top"
    BOTTOM = "bottom"
    BEFORE = "before"
    AFTER = "after"


class Rulebase(str, Enum):
    """Enum representing valid rulebase values."""

    PRE = "pre"
    POST = "post"


class Action(str, Enum):
    """Enum representing various network actions."""

    allow = "allow"
    deny = "deny"
    drop = "drop"
    reset_client = "reset-client"
    reset_server = "reset-server"
    reset_both = "reset-both"


# Component Models
class ProfileSetting(BaseModel):
    """Model for security profile settings."""

    group: Optional[List[str]] = Field(
        default_factory=lambda: ["best-practice"],
        description="The security profile group",
    )

    @field_validator("group")
    def validate_unique_items(cls, v):
        if v is not None and len(v) != len(set(v)):
            raise ValueError("List items in 'group' must be unique")
        return v


class SecurityRuleBaseModel(BaseModel):
    """Base model for Security Rules containing fields common to all operations."""

    model_config = ConfigDict(
        populate_by_name=True,
        validate_assignment=True,
        arbitrary_types_allowed=True,
    )

    name: constr(pattern=r"^[a-zA-Z0-9_ \.-]+$") = Field(
        ...,
        description="The name of the security rule",
    )
    disabled: bool = Field(False, description="Is the security rule disabled?")
    description: Optional[str] = Field(
        None, description="The description of the security rule"
    )
    tag: List[str] = Field(
        default_factory=list, description="The tags associated with the security rule"
    )
    from_: List[str] = Field(
        default_factory=lambda: ["any"],
        description="The source security zone(s)",
        alias="from",
    )
    source: List[str] = Field(
        default_factory=lambda: ["any"],
        description="The source addresses(es)",
    )
    negate_source: bool = Field(False, description="Negate the source address(es)?")
    source_user: List[str] = Field(
        default_factory=lambda: ["any"],
        description="List of source users and/or groups",
    )
    source_hip: List[str] = Field(
        default_factory=lambda: ["any"],
        description="The source Host Integrity Profile(s)",
    )
    to_: List[str] = Field(
        default_factory=lambda: ["any"],
        description="The destination security zone(s)",
        alias="to",
    )
    destination: List[str] = Field(
        default_factory=lambda: ["any"],
        description="The destination address(es)",
    )
    negate_destination: bool = Field(
        False, description="Negate the destination addresses(es)?"
    )
    destination_hip: List[str] = Field(
        default_factory=lambda: ["any"],
        description="The destination Host Integrity Profile(s)",
    )
    application: List[str] = Field(
        default_factory=lambda: ["any"],
        description="The application(s) being accessed",
    )
    service: List[str] = Field(
        default_factory=lambda: ["any"],
        description="The service(s) being accessed",
    )
    category: List[str] = Field(
        default_factory=lambda: ["any"],
        description="The URL categories being accessed",
    )
    action: Action = Field(
        default=Action.allow,
        description="The action to be taken when the rule is matched",
    )
    profile_setting: Optional[ProfileSetting] = None
    log_setting: Optional[str] = None
    schedule: Optional[str] = None
    log_start: Optional[bool] = None
    log_end: Optional[bool] = None

    folder: Optional[str] = Field(
        None,
        description="Folder",
        max_length=64,
        pattern=r"^[a-zA-Z\d\-_. ]+$",
    )
    snippet: Optional[str] = Field(
        None,
        description="Snippet",
        max_length=64,
        pattern=r"^[a-zA-Z\d\-_. ]+$",
    )
    device: Optional[str] = Field(
        None,
        description="Device",
        max_length=64,
        pattern=r"^[a-zA-Z\d\-_. ]+$",
    )

    # Common validators
    @field_validator(
        "from_",
        "source",
        "source_user",
        "source_hip",
        "to_",
        "destination",
        "destination_hip",
        "application",
        "service",
        "category",
        "tag",
        mode="before",
    )
    def ensure_list_of_strings(cls, v):
        if isinstance(v, str):
            v = [v]
        elif not isinstance(v, list):
            raise ValueError("Value must be a list of strings")
        if not all(isinstance(item, str) for item in v):
            raise ValueError("All items must be strings")
        return v

    @field_validator(
        "from_",
        "source",
        "source_user",
        "source_hip",
        "to_",
        "destination",
        "destination_hip",
        "application",
        "service",
        "category",
        "tag",
    )
    def ensure_unique_items(cls, v):
        if len(v) != len(set(v)):
            raise ValueError("List items must be unique")
        return v


class SecurityRuleCreateModel(SecurityRuleBaseModel):
    """Model for creating new Security Rules."""

    rulebase: Optional[Rulebase] = Field(
        None,
        description="Which rulebase to use (pre or post)",
    )

    @model_validator(mode="after")
    def validate_container(self) -> "SecurityRuleCreateModel":
        container_fields = ["folder", "snippet", "device"]
        provided = [
            field for field in container_fields if getattr(self, field) is not None
        ]
        if len(provided) != 1:
            raise ValueError(
                "Exactly one of 'folder', 'snippet', or 'device' must be provided."
            )
        return self


class SecurityRuleUpdateModel(SecurityRuleBaseModel):
    """Model for updating existing Security Rules with all fields optional."""

    rulebase: Optional[Rulebase] = None


class SecurityRuleResponseModel(SecurityRuleBaseModel):
    """Model for Security Rule responses, including the id field."""

    id: UUID = Field(
        ...,
        description="The UUID of the security rule",
        examples=["123e4567-e89b-12d3-a456-426655440000"],
    )


class SecurityRuleMoveModel(BaseModel):
    """Model for security rule move operations."""

    source_rule: UUID = Field(
        ...,
        description="UUID of the security rule to be moved",
    )
    destination: RuleMoveDestination = Field(
        ...,
        description="Where to move the rule (top, bottom, before, after)",
    )
    rulebase: Rulebase = Field(
        ...,
        description="Which rulebase to use (pre or post)",
    )
    destination_rule: Optional[str] = Field(
        None,
        description="UUID of the reference rule for before/after moves",
    )

    model_config = ConfigDict(
        validate_assignment=True,
        arbitrary_types_allowed=True,
    )

    @model_validator(mode="after")
    def validate_move_configuration(self) -> "SecurityRuleMoveModel":
        if self.destination in (
            RuleMoveDestination.BEFORE,
            RuleMoveDestination.AFTER,
        ):
            if not self.destination_rule:
                raise ValueError(
                    f"destination_rule is required when destination is '{self.destination.value}'"
                )
        elif self.destination_rule is not None:
            raise ValueError(
                f"destination_rule should not be provided when destination is '{self.destination.value}'"
            )
        return self
