from __future__ import annotations

import typing as T
import pydantic

from .valueobjects import *


class ENTBlobLifecycle(pydantic.BaseModel):
    """
    Immutable blob metadata using BlobUri as canonical identifier.

    Contains all essential information about a stored blob including size,
    timestamps, content type, and user-defined tags. The BlobUri provides
    a unified way to identify and reference the blob.

    All attributes are index-aligned with the versions list.

    """

    logical_name: str = pydantic.Field(
        description="Logical name of the blob. This is the name that is used to refer to the blob in the system. The logical name is the same for all versions of the blob.",
    )

    logical_path: str = pydantic.Field(
        description="Logical path of the blob. This is the path that is used to refer to the blob in the system. The logical path is the same for all versions of the blob.",
    )

    content_type: T.Optional[str] = pydantic.Field(
        default=None,
        description="MIME type of blob content. Consistent across versions",
        examples=[
            "application/octet-stream",
            "text/csv",
            "application/json",
            "application/x-pytorch",
        ],
    )

    versions: T.List[VOBlobMetadata] = pydantic.Field(
        description="List of all versions of this blob in chronological order (newest first)",
    )

    @pydantic.field_validator("versions", mode="before")
    def validate_versions(
        cls,
        v: T.List[VOBlobMetadata],
        info: pydantic.ValidationInfo,
    ) -> T.List[VOBlobMetadata]:
        """Validate version list consistency"""
        if not v:
            raise ValueError("Versions list cannot be empty")

        lifecycle_logical_path = info.data.get("logical_path")

        # Check all versions belong to same logical blob
        for i, version in enumerate(v):
            if version.uri.logical_path != lifecycle_logical_path:
                raise ValueError(
                    f"Path mismatch at index {i}: {version.uri.logical_path} != {lifecycle_logical_path}"
                )

        # Check version uniqueness
        version_ids = [ver.uri.version for ver in v]
        if len(set(version_ids)) != len(version_ids):
            raise ValueError("Duplicate versions found")

        return v

    # BUSINESS OPERATIONS
    def get_latest(self) -> VOBlobMetadata:
        """Get latest version metadata"""
        return self.versions[0]

    def get_version(self, version_id: str) -> VOBlobMetadata:
        """Get specific version metadata"""
        for version in self.versions:
            if version.uri.version == version_id:
                return version
        raise ValueError(f"Version '{version_id}' not found")

    def has_version(self, version_id: str) -> bool:
        """Check if version exists"""
        return any(v.uri.version == version_id for v in self.versions)

    def filter_by_tags(self, tag_filters: T.Dict[str, str]) -> T.List[VOBlobMetadata]:
        """Get versions matching tag filters"""
        return [v for v in self.versions if v.matches_tags(tag_filters)]

    def get_total_size(self) -> int:
        """Total size across all versions"""
        return sum(v.size_bytes for v in self.versions)

    def get_version_count(self) -> int:
        """Number of versions"""
        return len(self.versions)

    # UPDATES
    def add_version(
        self,
        new_version: VOBlobMetadata,
    ) -> "ENTBlobLifecycle":
        """Add new version (immutable) - maintains chronological order"""
        # Validate consistency
        if new_version.uri.logical_path != self.logical_path:
            raise ValueError("Version path doesn't match entity path")
        if new_version.uri.logical_name != self.logical_name:
            raise ValueError("Version name doesn't match entity name")
        if self.has_version(new_version.uri.version):
            raise ValueError(f"Version '{new_version.uri.version}' already exists")

        # Insert in ascending chronological order
        # NOTE - implmentation is O(N) for time and memory.
        new_versions = []
        inserted = False

        for existing in self.versions:
            if not inserted and new_version.created_at >= existing.created_at:
                new_versions.append(new_version)
                inserted = True
            new_versions.append(existing)

        if not inserted:
            new_versions.append(new_version)

        return self.model_copy(update={"versions": new_versions})

    def remove_version(self, version_id: str) -> "ENTBlobLifecycle":
        """Remove version (immutable)"""
        if not self.has_version(version_id):
            raise ValueError(f"Version '{version_id}' not found")
        if len(self.versions) == 1:
            raise ValueError("Cannot remove the last version")

        new_versions = [v for v in self.versions if v.uri.version != version_id]

        return self.model_copy(update={"versions": new_versions})

    @classmethod
    def create(cls, metadatas: T.List[VOBlobMetadata]) -> ENTBlobLifecycle:
        initial = metadatas[0]
        entity = cls(
            logical_path=initial.uri.logical_path,
            logical_name=initial.uri.logical_name,
            versions=[initial],
        )

        for metadata in metadatas[1:]:
            entity = entity.add_version(metadata)

        return entity
