"""Pydantic models for Copy-on-Write filesystem support."""

from __future__ import annotations

from datetime import datetime
from enum import Enum
from pathlib import Path

from pydantic import BaseModel, ConfigDict, Field, field_validator


class CoWBackendType(str, Enum):
    """Supported CoW filesystem backends."""

    BTRFS = "btrfs"
    ZFS = "zfs"


class PoolStatus(str, Enum):
    """Pool health status."""

    ONLINE = "online"
    DEGRADED = "degraded"
    OFFLINE = "offline"
    ERROR = "error"


class PoolConfig(BaseModel):
    """Configuration for initializing a CoW pool."""

    model_config = ConfigDict(frozen=True)

    name: str = Field(..., min_length=1, max_length=64, pattern=r"^[a-zA-Z0-9_-]+$")
    backend: CoWBackendType = CoWBackendType.BTRFS
    mount_point: Path = Field(..., description="Path to existing CoW filesystem mount")
    compression: str | None = Field(
        default=None, description="Compression algorithm (lz4, zstd, gzip, etc.)"
    )
    quota_mb: int | None = Field(default=None, ge=1, description="Pool quota in MB")
    description: str | None = None


class PoolState(BaseModel):
    """Runtime state of a CoW pool."""

    model_config = ConfigDict(frozen=True)

    name: str
    backend: CoWBackendType
    status: PoolStatus = PoolStatus.ONLINE
    mount_point: Path
    gaol_path: Path = Field(
        ..., description="Path to gaol subvolume/dataset within the pool"
    )
    total_bytes: int = Field(default=0, ge=0)
    used_bytes: int = Field(default=0, ge=0)
    free_bytes: int = Field(default=0, ge=0)
    compression: str | None = None
    created_at: datetime = Field(default_factory=datetime.now)

    @property
    def usage_percent(self) -> float:
        """Return usage percentage."""
        if self.total_bytes == 0:
            return 0.0
        return (self.used_bytes / self.total_bytes) * 100


class DatasetConfig(BaseModel):
    """Configuration for creating a dataset."""

    model_config = ConfigDict(frozen=True)

    name: str = Field(..., min_length=1, max_length=64, pattern=r"^[a-zA-Z0-9_-]+$")
    pool: str = Field(..., description="Name of parent pool")
    quota_mb: int | None = Field(default=None, ge=1, description="Dataset quota in MB")
    compression: str | None = Field(
        default=None, description="Override pool compression"
    )
    description: str | None = None


class DatasetState(BaseModel):
    """Runtime state of a dataset."""

    model_config = ConfigDict(frozen=True)

    name: str
    pool: str
    path: Path = Field(..., description="Filesystem path to dataset")
    quota_mb: int | None = None
    used_bytes: int = Field(default=0, ge=0)
    referenced_bytes: int = Field(default=0, ge=0)
    compression: str | None = None
    created_at: datetime = Field(default_factory=datetime.now)
    snapshots: list[str] = Field(default_factory=list)

    @property
    def full_name(self) -> str:
        """Return pool/dataset name."""
        return f"{self.pool}/{self.name}"


class Snapshot(BaseModel):
    """A point-in-time snapshot of a dataset."""

    model_config = ConfigDict(frozen=True)

    name: str = Field(..., min_length=1, max_length=128)
    dataset: str
    pool: str
    created_at: datetime = Field(default_factory=datetime.now)
    used_bytes: int = Field(default=0, ge=0, description="Space exclusive to snapshot")
    referenced_bytes: int = Field(default=0, ge=0, description="Total data referenced")
    readonly: bool = Field(default=True)

    @property
    def full_name(self) -> str:
        """Return pool/dataset@snapshot name."""
        return f"{self.pool}/{self.dataset}@{self.name}"

    @field_validator("name")
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Ensure snapshot name doesn't contain @ or /."""
        if "@" in v or "/" in v:
            raise ValueError("Snapshot name cannot contain '@' or '/'")
        return v


class CloneConfig(BaseModel):
    """Configuration for creating a clone from a snapshot."""

    model_config = ConfigDict(frozen=True)

    name: str = Field(..., min_length=1, max_length=64, pattern=r"^[a-zA-Z0-9_-]+$")
    source_snapshot: str = Field(
        ..., description="Full snapshot name (pool/dataset@snapshot)"
    )
    pool: str = Field(..., description="Pool to create clone in")
    description: str | None = None

    @field_validator("source_snapshot")
    @classmethod
    def validate_source(cls, v: str) -> str:
        """Ensure source_snapshot contains @ separator."""
        if "@" not in v:
            raise ValueError("source_snapshot must be in format 'pool/dataset@snapshot'")
        return v


class SendOptions(BaseModel):
    """Options for send/receive operations."""

    model_config = ConfigDict(frozen=True)

    base_snapshot: str | None = Field(
        default=None, description="Base snapshot for incremental send"
    )
    compressed: bool = Field(default=True, description="Compress the stream")
    raw: bool = Field(default=False, description="Send raw encrypted data (ZFS)")


class PoolsStore(BaseModel):
    """Persisted pool registry."""

    model_config = ConfigDict(frozen=False)

    version: int = 1
    pools: dict[str, PoolState] = Field(default_factory=dict)

    def add_pool(self, pool: PoolState) -> None:
        """Add or update a pool."""
        self.pools[pool.name] = pool

    def get_pool(self, name: str) -> PoolState | None:
        """Get a pool by name."""
        return self.pools.get(name)

    def remove_pool(self, name: str) -> bool:
        """Remove a pool. Returns True if removed."""
        if name in self.pools:
            del self.pools[name]
            return True
        return False

    def list_pools(self) -> list[PoolState]:
        """List all pools."""
        return list(self.pools.values())


class DatasetsStore(BaseModel):
    """Persisted dataset registry."""

    model_config = ConfigDict(frozen=False)

    version: int = 1
    datasets: dict[str, DatasetState] = Field(default_factory=dict)

    def add_dataset(self, dataset: DatasetState) -> None:
        """Add or update a dataset."""
        self.datasets[dataset.full_name] = dataset

    def get_dataset(self, pool: str, name: str) -> DatasetState | None:
        """Get a dataset by pool/name."""
        return self.datasets.get(f"{pool}/{name}")

    def remove_dataset(self, pool: str, name: str) -> bool:
        """Remove a dataset. Returns True if removed."""
        key = f"{pool}/{name}"
        if key in self.datasets:
            del self.datasets[key]
            return True
        return False

    def list_datasets(self, pool: str | None = None) -> list[DatasetState]:
        """List all datasets, optionally filtered by pool."""
        datasets = list(self.datasets.values())
        if pool:
            datasets = [d for d in datasets if d.pool == pool]
        return datasets
