"""Auto-generated from proto/nexus/core/metadata.proto - DO NOT EDIT.

This module is generated by: python scripts/gen_metadata.py
SSOT: proto/nexus/core/metadata.proto

Central metadata mapping between FileMetadata and serialization formats.
Proto/JSON methods are auto-generated. SQL methods are manual (different schema).
"""

from __future__ import annotations

import logging
from contextlib import suppress
from datetime import datetime
from typing import TYPE_CHECKING, Any

from nexus.contracts.constants import ROOT_ZONE_ID

# Known FileMetadata field names (generated from proto).
# Used by from_json() to strip unknown keys from external dicts.
_KNOWN_FIELDS: frozenset[str] = frozenset(
    {
        "path",
        "size",
        "content_id",
        "mime_type",
        "created_at",
        "modified_at",
        "version",
        "zone_id",
        "owner_id",
        "entry_type",
        "target_zone_id",
        "ttl_seconds",
        "last_writer_address",
        "link_target",
        "gen",
    }
)

if TYPE_CHECKING:
    from nexus.contracts.metadata import FileMetadata

logger = logging.getLogger(__name__)


def _to_naive(dt: datetime | None) -> datetime | None:
    """Strip timezone from datetime (SQLite stores naive UTC)."""
    if dt is None:
        return None
    return dt.replace(tzinfo=None) if dt.tzinfo else dt


def _utcnow_naive() -> datetime:
    """Return current UTC time as naive datetime (for SQLite compat)."""
    from datetime import UTC

    return datetime.now(UTC).replace(tzinfo=None)


# ---------------------------------------------------------------------------
# Field name mapping: proto field -> SQLAlchemy column (manual, not generated)
# ---------------------------------------------------------------------------

PROTO_TO_SQL: dict[str, str | None] = {
    "path": "virtual_path",
    "size": "size_bytes",
    "content_id": "content_id",
    "mime_type": "file_type",
    "created_at": "created_at",
    "modified_at": "updated_at",
    "version": "current_version",
    "gen": None,  # Rust/metastore generation, not persisted in FilePathModel
    "zone_id": "zone_id",
    "created_by": None,  # TODO(#1246): Add to FilePathModel
    "entry_type": None,  # TODO(#1246): Add to FilePathModel
    "target_zone_id": None,  # TODO(#1246): Add to FilePathModel
    "owner_id": "posix_uid",
    "last_writer_address": None,  # SQL backend (FilePathModel) doesn't currently persist last writer; add a column when needed.
}


class MetadataMapper:
    """Centralized mapping between FileMetadata and other representations.

    Proto/JSON methods are auto-generated from proto field definitions.
    SQL methods are manual (different column name mapping).
    """

    # -- Proto serialization (GENERATED) ------------------------------------

    @staticmethod
    def to_proto(metadata: FileMetadata) -> Any:
        """Convert FileMetadata dataclass to protobuf message."""
        from nexus.core import metadata_pb2

        return metadata_pb2.FileMetadata(
            path=metadata.path,
            size=metadata.size,
            content_id=metadata.content_id or "",
            mime_type=metadata.mime_type or "",
            created_at=metadata.created_at.isoformat() if metadata.created_at else "",
            modified_at=metadata.modified_at.isoformat() if metadata.modified_at else "",
            version=metadata.version,
            zone_id=metadata.zone_id or "",
            owner_id=metadata.owner_id or "",
            entry_type=metadata_pb2.DirEntryType.Name(metadata.entry_type),
            target_zone_id=metadata.target_zone_id or "",
            ttl_seconds=metadata.ttl_seconds,
            last_writer_address=metadata.last_writer_address or "",
            link_target=metadata.link_target or "",
            gen=metadata.gen,
        )

    @staticmethod
    def from_proto(proto: Any) -> FileMetadata:
        """Convert protobuf message to FileMetadata dataclass."""
        from nexus.contracts.metadata import FileMetadata

        created_at = None
        if proto.created_at:
            with suppress(ValueError):
                created_at = datetime.fromisoformat(proto.created_at)
        modified_at = None
        if proto.modified_at:
            with suppress(ValueError):
                modified_at = datetime.fromisoformat(proto.modified_at)

        return FileMetadata(
            path=proto.path,
            size=proto.size,
            content_id=proto.content_id or None,
            mime_type=proto.mime_type or None,
            created_at=created_at,
            modified_at=modified_at,
            version=proto.version,
            zone_id=proto.zone_id or None,
            owner_id=proto.owner_id or None,
            entry_type=proto.entry_type,
            target_zone_id=proto.target_zone_id or None,
            ttl_seconds=proto.ttl_seconds,
            last_writer_address=proto.last_writer_address or None,
            link_target=proto.link_target or None,
            gen=proto.gen,
        )

    # -- JSON serialization (GENERATED) -------------------------------------

    @staticmethod
    def to_json(metadata: FileMetadata) -> dict[str, Any]:
        """Convert FileMetadata to JSON-serializable dict."""
        return {
            "path": metadata.path,
            "size": metadata.size,
            "content_id": metadata.content_id,
            "mime_type": metadata.mime_type,
            "created_at": metadata.created_at.isoformat() if metadata.created_at else None,
            "modified_at": metadata.modified_at.isoformat() if metadata.modified_at else None,
            "version": metadata.version,
            "zone_id": metadata.zone_id,
            "owner_id": metadata.owner_id,
            "entry_type": metadata.entry_type,
            "target_zone_id": metadata.target_zone_id,
            "ttl_seconds": metadata.ttl_seconds,
            "last_writer_address": metadata.last_writer_address,
            "link_target": metadata.link_target,
            "gen": metadata.gen,
        }

    @staticmethod
    def from_json(obj: dict[str, Any]) -> FileMetadata:
        """Convert JSON dict to FileMetadata dataclass."""
        from nexus.contracts.metadata import FileMetadata

        # Migration: convert legacy is_directory -> entry_type
        if "is_directory" in obj:
            is_dir = obj.pop("is_directory")
            if "entry_type" not in obj:
                obj["entry_type"] = 1 if is_dir else 0

        # Strip unknown keys (forward compatibility with older/newer proto versions)
        obj = {k: v for k, v in obj.items() if k in _KNOWN_FIELDS}

        if obj.get("created_at"):
            obj["created_at"] = datetime.fromisoformat(obj["created_at"])
        if obj.get("modified_at"):
            obj["modified_at"] = datetime.fromisoformat(obj["modified_at"])
        return FileMetadata(**obj)

    # -- SQLAlchemy column values (MANUAL — different schema) ---------------

    @staticmethod
    def to_file_path_values(
        metadata: FileMetadata,
        *,
        include_version: bool = True,
    ) -> dict[str, Any]:
        """Convert FileMetadata to dict of FilePathModel column values.

        Keys are FilePathModel column names (not proto field names).
        """
        values: dict[str, Any] = {
            "virtual_path": metadata.path,
            "size_bytes": metadata.size or 0,
            "content_id": metadata.content_id,
            "file_type": metadata.mime_type,
            "created_at": _to_naive(metadata.created_at) or _utcnow_naive(),
            "updated_at": _to_naive(metadata.modified_at) or _utcnow_naive(),
            "zone_id": metadata.zone_id or ROOT_ZONE_ID,
            "posix_uid": metadata.owner_id,
        }
        if include_version:
            values["current_version"] = 1
        return values

    @staticmethod
    def to_file_path_update_values(metadata: FileMetadata) -> dict[str, Any]:
        """Convert FileMetadata to dict for UPDATE operations."""
        return {
            "size_bytes": metadata.size or 0,
            "content_id": metadata.content_id,
            "file_type": metadata.mime_type,
            "updated_at": _to_naive(metadata.modified_at) or _utcnow_naive(),
        }
