"""Pydantic models for service migration.

This module defines data structures for parsing systemd services,
discovering dependencies, and analyzing migration requirements.
"""

from __future__ import annotations

from enum import Enum
from pathlib import Path
from typing import Any

from pydantic import BaseModel, Field


class UnitSection(BaseModel):
    """Parsed [Unit] section from a systemd service file."""

    description: str | None = None
    documentation: list[str] = Field(default_factory=list)
    after: list[str] = Field(default_factory=list)
    before: list[str] = Field(default_factory=list)
    wants: list[str] = Field(default_factory=list)
    requires: list[str] = Field(default_factory=list)
    conflicts: list[str] = Field(default_factory=list)
    binds_to: list[str] = Field(default_factory=list)
    part_of: list[str] = Field(default_factory=list)
    condition_path_exists: list[str] = Field(default_factory=list)


class ServiceSection(BaseModel):
    """Parsed [Service] section from a systemd service file."""

    type: str = "simple"
    exec_start: str | list[str] | None = None
    exec_start_pre: list[str] = Field(default_factory=list)
    exec_start_post: list[str] = Field(default_factory=list)
    exec_stop: str | list[str] | None = None
    exec_stop_post: list[str] = Field(default_factory=list)
    exec_reload: str | list[str] | None = None
    user: str = "root"
    group: str | None = None
    working_directory: str | None = None
    root_directory: str | None = None
    environment: dict[str, str] = Field(default_factory=dict)
    environment_file: list[str] = Field(default_factory=list)
    state_directory: list[str] = Field(default_factory=list)
    cache_directory: list[str] = Field(default_factory=list)
    logs_directory: list[str] = Field(default_factory=list)
    configuration_directory: list[str] = Field(default_factory=list)
    runtime_directory: list[str] = Field(default_factory=list)
    restart: str = "no"
    restart_sec: int | None = None
    timeout_start_sec: int | None = None
    timeout_stop_sec: int | None = None
    pid_file: str | None = None
    standard_output: str | None = None
    standard_error: str | None = None
    syslog_identifier: str | None = None
    # Security/capability related
    capability_bounding_set: list[str] = Field(default_factory=list)
    ambient_capabilities: list[str] = Field(default_factory=list)
    no_new_privileges: bool = False
    private_tmp: bool = False
    private_network: bool = False
    protect_system: str | None = None
    protect_home: str | None = None
    read_only_paths: list[str] = Field(default_factory=list)
    read_write_paths: list[str] = Field(default_factory=list)
    bind_paths: list[str] = Field(default_factory=list)
    bind_read_only_paths: list[str] = Field(default_factory=list)
    # Limit related
    limit_nofile: str | None = None
    limit_nproc: str | None = None
    limit_memlock: str | None = None
    # Other
    remain_after_exit: bool = False
    kill_mode: str | None = None
    kill_signal: str | None = None


class ServiceInstallSection(BaseModel):
    """Parsed [Install] section from a systemd service file."""

    wanted_by: list[str] = Field(default_factory=list)
    required_by: list[str] = Field(default_factory=list)
    alias: list[str] = Field(default_factory=list)
    also: list[str] = Field(default_factory=list)


class ParsedSystemdService(BaseModel):
    """Complete parsed systemd service file."""

    name: str
    """Service name (without .service suffix)."""

    file_path: Path
    """Path to the original service file."""

    unit: UnitSection = Field(default_factory=UnitSection)
    service: ServiceSection = Field(default_factory=ServiceSection)
    install: ServiceInstallSection = Field(default_factory=ServiceInstallSection)

    # Template service info
    is_template: bool = False
    """True if this is a template service (foo@.service)."""

    template_instance: str | None = None
    """Instance parameter for template services (e.g., 'instance' in foo@instance.service)."""

    # Drop-in files merged
    dropin_files: list[Path] = Field(default_factory=list)
    """List of drop-in configuration files that were merged."""

    # Raw sections for any non-standard keys
    raw_sections: dict[str, dict[str, Any]] = Field(default_factory=dict)

    def get_binary_path(self) -> str | None:
        """Extract the binary path from ExecStart."""
        exec_start = self.service.exec_start
        if not exec_start:
            return None
        # Handle list format (multiple ExecStart lines)
        if isinstance(exec_start, list):
            exec_start = exec_start[0] if exec_start else None
        if not exec_start:
            return None
        # Handle various prefix characters: -, @, +, !, !!
        cmd = exec_start.lstrip("-@+!").strip()
        # Split on whitespace to get the binary
        parts = cmd.split()
        if not parts:
            return None
        return parts[0]


class DiscoveredDependencies(BaseModel):
    """Dependencies discovered from analyzing a service."""

    # Package dependencies
    packages: list[str] = Field(default_factory=list)
    """Packages required by the service binary and dependencies."""

    binary_package_map: dict[str, str] = Field(default_factory=dict)
    """Mapping of binary paths to their packages."""

    # File dependencies
    config_files: list[str] = Field(default_factory=list)
    """Configuration files needed by the service."""

    config_directories: list[str] = Field(default_factory=list)
    """Configuration directories needed by the service."""

    # Data and state
    data_directories: list[str] = Field(default_factory=list)
    """Data directories that should be mounted or copied."""

    state_directories: list[str] = Field(default_factory=list)
    """State directories (typically in /var/lib)."""

    # Network
    listening_ports: list[int] = Field(default_factory=list)
    """TCP/UDP ports the service listens on."""

    # Related services
    socket_units: list[str] = Field(default_factory=list)
    """Associated .socket units for socket activation."""

    timer_units: list[str] = Field(default_factory=list)
    """Associated .timer units."""

    # Capabilities and privileges
    required_capabilities: list[str] = Field(default_factory=list)
    """Linux capabilities required by the service."""


class MigrationWarningSeverity(str, Enum):
    """Severity level for migration warnings."""

    INFO = "info"
    WARNING = "warning"
    CRITICAL = "critical"


class MigrationWarning(BaseModel):
    """Warning about potential issues during migration."""

    category: str
    """Category of warning (e.g., 'socket_activation', 'kernel_access')."""

    message: str
    """Human-readable warning message."""

    severity: MigrationWarningSeverity = MigrationWarningSeverity.WARNING
    """Severity level."""

    suggestion: str | None = None
    """Suggested action to address the warning."""

    def __str__(self) -> str:
        """Format warning for display."""
        prefix = {
            MigrationWarningSeverity.INFO: "INFO",
            MigrationWarningSeverity.WARNING: "WARN",
            MigrationWarningSeverity.CRITICAL: "CRITICAL",
        }[self.severity]
        msg = f"[{prefix}] {self.category}: {self.message}"
        if self.suggestion:
            msg += f"\n  Suggestion: {self.suggestion}"
        return msg


class MigrationAnalysis(BaseModel):
    """Complete analysis of a service migration."""

    service: ParsedSystemdService
    """The parsed systemd service."""

    dependencies: DiscoveredDependencies
    """Discovered dependencies."""

    warnings: list[MigrationWarning] = Field(default_factory=list)
    """Warnings about migration issues."""

    # Computed recommendations
    recommended_runtime: str = "nspawn"
    """Recommended container runtime."""

    recommended_distro: str = "trixie"
    """Recommended base distribution."""

    requires_privileged: bool = False
    """Whether the container needs privileged mode."""

    requires_host_network: bool = False
    """Whether the service needs host network access."""

    can_migrate: bool = True
    """Whether the service can be safely migrated."""

    migration_blockers: list[str] = Field(default_factory=list)
    """Reasons that prevent migration (if can_migrate is False)."""

    @property
    def has_critical_warnings(self) -> bool:
        """Check if there are any critical warnings."""
        return any(w.severity == MigrationWarningSeverity.CRITICAL for w in self.warnings)


class WellKnownService(BaseModel):
    """Pre-configured knowledge about a well-known service."""

    name: str
    """Service name."""

    packages: list[str] = Field(default_factory=list)
    """Required packages."""

    config_paths: list[str] = Field(default_factory=list)
    """Configuration file/directory paths."""

    data_paths: list[str] = Field(default_factory=list)
    """Data directory paths."""

    ports: list[int] = Field(default_factory=list)
    """Default listening ports."""

    notes: str | None = None
    """Additional migration notes."""


# Well-known services registry
WELLKNOWN_SERVICES: dict[str, WellKnownService] = {
    "nginx": WellKnownService(
        name="nginx",
        packages=["nginx"],
        config_paths=["/etc/nginx/"],
        data_paths=["/var/www/", "/usr/share/nginx/html/"],
        ports=[80, 443],
    ),
    "apache2": WellKnownService(
        name="apache2",
        packages=["apache2"],
        config_paths=["/etc/apache2/"],
        data_paths=["/var/www/"],
        ports=[80, 443],
    ),
    "postgresql": WellKnownService(
        name="postgresql",
        packages=["postgresql"],
        config_paths=["/etc/postgresql/"],
        data_paths=["/var/lib/postgresql/"],
        ports=[5432],
        notes="Database files should be migrated carefully with pg_dump",
    ),
    "mysql": WellKnownService(
        name="mysql",
        packages=["mysql-server"],
        config_paths=["/etc/mysql/"],
        data_paths=["/var/lib/mysql/"],
        ports=[3306],
        notes="Use mysqldump for data migration",
    ),
    "mariadb": WellKnownService(
        name="mariadb",
        packages=["mariadb-server"],
        config_paths=["/etc/mysql/"],
        data_paths=["/var/lib/mysql/"],
        ports=[3306],
        notes="Use mysqldump for data migration",
    ),
    "redis-server": WellKnownService(
        name="redis-server",
        packages=["redis-server"],
        config_paths=["/etc/redis/"],
        data_paths=["/var/lib/redis/"],
        ports=[6379],
    ),
    "memcached": WellKnownService(
        name="memcached",
        packages=["memcached"],
        config_paths=["/etc/memcached.conf"],
        data_paths=[],
        ports=[11211],
    ),
    "sshd": WellKnownService(
        name="sshd",
        packages=["openssh-server"],
        config_paths=["/etc/ssh/"],
        data_paths=[],
        ports=[22],
        notes="Container SSH requires proper key management",
    ),
    "ssh": WellKnownService(
        name="ssh",
        packages=["openssh-server"],
        config_paths=["/etc/ssh/"],
        data_paths=[],
        ports=[22],
        notes="Container SSH requires proper key management",
    ),
    "docker": WellKnownService(
        name="docker",
        packages=["docker.io"],
        config_paths=["/etc/docker/"],
        data_paths=["/var/lib/docker/"],
        ports=[2375, 2376],
        notes="Docker-in-container requires privileged mode",
    ),
    "containerd": WellKnownService(
        name="containerd",
        packages=["containerd"],
        config_paths=["/etc/containerd/"],
        data_paths=["/var/lib/containerd/"],
        ports=[],
    ),
    "haproxy": WellKnownService(
        name="haproxy",
        packages=["haproxy"],
        config_paths=["/etc/haproxy/"],
        data_paths=[],
        ports=[80, 443, 8080],
    ),
    "bind9": WellKnownService(
        name="bind9",
        packages=["bind9"],
        config_paths=["/etc/bind/"],
        data_paths=["/var/cache/bind/"],
        ports=[53],
    ),
    "named": WellKnownService(
        name="named",
        packages=["bind9"],
        config_paths=["/etc/bind/"],
        data_paths=["/var/cache/bind/"],
        ports=[53],
    ),
    "postfix": WellKnownService(
        name="postfix",
        packages=["postfix"],
        config_paths=["/etc/postfix/"],
        data_paths=["/var/spool/postfix/", "/var/mail/"],
        ports=[25, 587],
    ),
    "dovecot": WellKnownService(
        name="dovecot",
        packages=["dovecot-core", "dovecot-imapd"],
        config_paths=["/etc/dovecot/"],
        data_paths=["/var/mail/"],
        ports=[143, 993, 110, 995],
    ),
    "grafana-server": WellKnownService(
        name="grafana-server",
        packages=["grafana"],
        config_paths=["/etc/grafana/"],
        data_paths=["/var/lib/grafana/"],
        ports=[3000],
    ),
    "prometheus": WellKnownService(
        name="prometheus",
        packages=["prometheus"],
        config_paths=["/etc/prometheus/"],
        data_paths=["/var/lib/prometheus/"],
        ports=[9090],
    ),
    "elasticsearch": WellKnownService(
        name="elasticsearch",
        packages=["elasticsearch"],
        config_paths=["/etc/elasticsearch/"],
        data_paths=["/var/lib/elasticsearch/"],
        ports=[9200, 9300],
    ),
    "rabbitmq-server": WellKnownService(
        name="rabbitmq-server",
        packages=["rabbitmq-server"],
        config_paths=["/etc/rabbitmq/"],
        data_paths=["/var/lib/rabbitmq/"],
        ports=[5672, 15672],
    ),
    "mongod": WellKnownService(
        name="mongod",
        packages=["mongodb-org-server"],
        config_paths=["/etc/mongod.conf"],
        data_paths=["/var/lib/mongodb/"],
        ports=[27017],
    ),
    "influxdb": WellKnownService(
        name="influxdb",
        packages=["influxdb"],
        config_paths=["/etc/influxdb/"],
        data_paths=["/var/lib/influxdb/"],
        ports=[8086],
    ),
    "jenkins": WellKnownService(
        name="jenkins",
        packages=["jenkins"],
        config_paths=["/etc/default/jenkins"],
        data_paths=["/var/lib/jenkins/"],
        ports=[8080],
    ),
    "gitlab-runsvdir": WellKnownService(
        name="gitlab-runsvdir",
        packages=["gitlab-ce"],
        config_paths=["/etc/gitlab/"],
        data_paths=["/var/opt/gitlab/"],
        ports=[80, 443, 22],
    ),
    "cups": WellKnownService(
        name="cups",
        packages=["cups"],
        config_paths=["/etc/cups/"],
        data_paths=["/var/spool/cups/"],
        ports=[631],
    ),
    "openvpn": WellKnownService(
        name="openvpn",
        packages=["openvpn"],
        config_paths=["/etc/openvpn/"],
        data_paths=[],
        ports=[1194],
        notes="Requires TUN/TAP device access",
    ),
    "wireguard": WellKnownService(
        name="wireguard",
        packages=["wireguard-tools"],
        config_paths=["/etc/wireguard/"],
        data_paths=[],
        ports=[51820],
        notes="Requires kernel module and NET_ADMIN capability",
    ),
    "tor": WellKnownService(
        name="tor",
        packages=["tor"],
        config_paths=["/etc/tor/"],
        data_paths=["/var/lib/tor/"],
        ports=[9050, 9051],
    ),
    "squid": WellKnownService(
        name="squid",
        packages=["squid"],
        config_paths=["/etc/squid/"],
        data_paths=["/var/spool/squid/"],
        ports=[3128],
    ),
    "unbound": WellKnownService(
        name="unbound",
        packages=["unbound"],
        config_paths=["/etc/unbound/"],
        data_paths=[],
        ports=[53],
    ),
    "dnsmasq": WellKnownService(
        name="dnsmasq",
        packages=["dnsmasq"],
        config_paths=["/etc/dnsmasq.conf", "/etc/dnsmasq.d/"],
        data_paths=[],
        ports=[53, 67],
    ),
    "fail2ban": WellKnownService(
        name="fail2ban",
        packages=["fail2ban"],
        config_paths=["/etc/fail2ban/"],
        data_paths=["/var/lib/fail2ban/"],
        ports=[],
        notes="Requires access to host logs and iptables",
    ),
    "jellyfin": WellKnownService(
        name="jellyfin",
        packages=["jellyfin", "jellyfin-web", "jellyfin-ffmpeg7"],
        config_paths=["/etc/jellyfin/", "/etc/default/jellyfin"],
        data_paths=["/var/lib/jellyfin/", "/var/cache/jellyfin/", "/var/log/jellyfin/"],
        ports=[8096, 8920],
        notes="Requires ffmpeg symlinks: ln -sf /usr/lib/jellyfin-ffmpeg/ffmpeg /usr/bin/ffmpeg",
    ),
}
