#!/usr/bin/env python
# Copyright 2024 NetBox Labs Inc
"""Device Discovery Policy Models."""

import re
import time
import uuid
from enum import Enum
from typing import Any

from croniter import CroniterBadCronError, croniter
from pydantic import BaseModel, Field, field_validator


class Status(Enum):
    """Enumeration for status."""

    NEW = "new"
    RUNNING = "running"
    FINISHED = "finished"
    FAILED = "failed"


class InterfacePattern(BaseModel):
    """Model for interface type pattern matching."""

    match: str = Field(
        description="Regular expression pattern to match interface names"
    )
    type: str = Field(description="Interface type to assign when pattern matches")

    @field_validator("match")
    @classmethod
    def validate_regex(cls, value: str) -> str:
        """
        Validate the regex pattern at configuration load time.

        Args:
        ----
            value: The regex pattern string.

        Raises:
        ------
            ValueError: If the regex pattern is invalid.

        Returns:
        -------
            str: The validated regex pattern.

        """
        try:
            re.compile(value)
        except re.error as e:
            raise ValueError(f"Invalid regex pattern: {e}")
        return value


class ObjectParameters(BaseModel):
    """Model for object parameters."""

    comments: str | None = Field(default=None, description="Comments, optional")
    description: str | None = Field(default=None, description="Description, optional")
    tags: list[str] | None = Field(default=None, description="Tags, optional")


class DeviceParameters(ObjectParameters):
    """Model for device specific parameters."""

    model: str | None = Field(
        default=None, description="Device model override, optional"
    )
    manufacturer: str | None = Field(
        default=None, description="Device manufacturer override, optional"
    )
    platform: str | None = Field(
        default=None, description="Device platform override, optional"
    )


class TenantParameters(ObjectParameters):
    """Model for Tenant parameters."""

    name: str
    group: str | None = Field(default=None, description="Tenant group, optional")


class VlanParameters(ObjectParameters):
    """Model for VLAN parameters."""

    group: str | None = Field(default=None, description="VLAN group, optional")
    tenant: str | TenantParameters | None = Field(
        default=None, description="VLAN tenant, optional"
    )
    role: str | None = Field(default=None, description="VLAN role, optional")


class IpamParameters(ObjectParameters):
    """Model for IPAM parameters."""

    role: str | None = Field(default=None, description="IPAM role, optional")
    tenant: str | TenantParameters | None = Field(
        default=None, description="IPAM tenant, optional"
    )
    vrf: str | None = Field(default=None, description="IPAM VRF, optional")


class Defaults(BaseModel):
    """Model for default configuration."""

    site: str | None = Field(default="undefined", description="Site name, optional")
    role: str | None = Field(
        default="undefined", description="Device Role name, optional"
    )
    if_type: str | None = Field(default="other", description="Interface type, optional")
    interface_patterns: list[InterfacePattern] | None = Field(
        default=None,
        description="Interface type patterns for name-based matching, optional",
    )
    location: str | None = Field(default=None, description="Location name, optional")
    tenant: str | TenantParameters | None = Field(
        default=None, description="Tenant, optional"
    )
    tags: list[str] | None = Field(default=None, description="Tags, optional")
    device: DeviceParameters | None = Field(
        default=None, description="Device parameters, optional"
    )
    interface: ObjectParameters | None = Field(
        default=None, description="Interface parameters, optional"
    )
    ipaddress: IpamParameters | None = Field(
        default=None, description="IP Address parameters, optional"
    )
    prefix: IpamParameters | None = Field(
        default=None, description="Prefix parameters, optional"
    )
    vlan: VlanParameters | None = Field(
        default=None, description="VLAN parameters, optional"
    )


class Options(BaseModel):
    """Model for discovery options."""

    platform_omit_version: bool | None = Field(
        default=False, description="Omit platform version, optional"
    )
    port_scan_ports: list[int] | None = Field(
        default=[22, 23, 80, 443, 830, 57400],
        description="TCP ports to probe before discovery (e.g. [22,23,80,443])",
    )
    port_scan_timeout: float | None = Field(
        default=0.5,
        description="TCP port probe timeout in seconds",
    )
    capture_running_config: bool | None = Field(
        default=False, description="Capture running configuration, optional"
    )
    capture_startup_config: bool | None = Field(
        default=False, description="Capture startup/saved configuration, optional"
    )


class Config(BaseModel):
    """Model for discovery configuration."""

    schedule: str | None = Field(default=None, description="cron interval, optional")
    defaults: Defaults | None = Field(
        default=None, description="Default configuration, optional"
    )
    options: Options | None = Field(
        default=None, description="Discovery options, optional"
    )

    @field_validator("schedule")
    @classmethod
    def validate_cron(cls, value):
        """
        Validate the cron schedule format.

        Args:
        ----
            value: The cron schedule value.

        Raises:
        ------
            ValueError: If the cron schedule format is invalid.

        """
        try:
            croniter(value)
        except CroniterBadCronError:
            raise ValueError("Invalid cron schedule format.")
        return value


class Napalm(BaseModel):
    """Model for NAPALM configuration."""

    driver: str | None = Field(default=None, description="Driver name, optional")
    hostname: str
    username: str
    password: str
    timeout: int = 60
    optional_args: dict[str, Any] | None = Field(
        default=None, description="Optional arguments"
    )
    override_defaults: Defaults | None = Field(
        default=None,
        description="Override default configuration for this host, optional",
    )


class Policy(BaseModel):
    """Model for a policy configuration."""

    config: Config | None = Field(default=None, description="Configuration data")
    scope: list[Napalm]


class PolicyRequest(BaseModel):
    """Model for a policy request."""

    policies: dict[str, Policy]


class RunStatus(str, Enum):
    """Run status enumeration."""

    RUNNING = "running"
    COMPLETED = "completed"
    FAILED = "failed"


class Run(BaseModel):
    """Model for a single run execution."""

    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    policy_id: str
    status: RunStatus
    reason: str = ""
    entity_count: int = 0
    metadata: dict[str, str] = Field(default_factory=dict)
    created_at: int = Field(default_factory=time.time_ns)
    updated_at: int = Field(default_factory=time.time_ns)


class PolicyStatus(BaseModel):
    """Status response for a policy with run history."""

    name: str
    status: str  # Derived from latest run
    runs: list[Run]
