# 元数据存储 - 定义 doc_metadata 表结构、存储与更新方法

from datetime import datetime, timezone
from typing import Any, Optional, Union

from pymongo.collection import Collection
from pymongo.cursor import Cursor
from pymongo.results import DeleteResult, InsertOneResult, UpdateResult

from .mongo_repository_base import MongoRepositoryBase

# ---------------------------------------------------------------------------
# 集合名与表结构：字段名常量与说明
# ---------------------------------------------------------------------------

# 数据库名称
DATABASE_NAME = "nutricare"

# 元数据集合名（MongoDB collection）
METADATA_COLLECTION = "doc_metadata"

# 唯一键：存储子目录 + 相对路径，用于 upsert
KEY_STORAGE_SUBDIR = "storage_subdir"
KEY_RELATIVE_PATH = "relative_path"

# 文档字段
KEY_TICKET_ID = "ticket_id"
KEY_TASK_ID = "task_id"
KEY_SIZE = "size"
KEY_CONTENT_HASH = "content_hash"
KEY_MD5 = "md5"
KEY_PHASH = "phash"
KEY_MIME_TYPE = "mime_type"
KEY_STATUS = "status"
KEY_METADATA = "metadata"
KEY_CREATED_AT = "created_at"
KEY_UPDATED_AT = "updated_at"

# 建议唯一索引：(storage_subdir, relative_path)
INDEX_KEYS = [KEY_STORAGE_SUBDIR, KEY_RELATIVE_PATH]


def _now_utc() -> datetime:
    """当前 UTC 时间。"""
    return datetime.now(timezone.utc)


def make_document(
    *,
    storage_subdir: str,
    relative_path: str,
    size: Optional[int] = None,
    content_hash: Optional[str] = None,
    md5: Optional[str] = None,
    phash: Optional[str] = None,
    mime_type: Optional[str] = None,
    ticket_id: Optional[str] = None,
    task_id: Optional[str] = None,
    status: Optional[str] = None,
    metadata: Optional[dict[str, Any]] = None,
    created_at: Optional[datetime] = None,
    updated_at: Optional[datetime] = None,
) -> dict[str, Any]:
    """
    构造符合元数据表结构的文档（不包含 _id）。
    用于存储前校验与统一时间戳。
    """
    now = _now_utc()
    doc: dict[str, Any] = {
        KEY_STORAGE_SUBDIR: storage_subdir.strip(),
        KEY_RELATIVE_PATH: relative_path.replace("\\", "/").strip().lstrip("/"),
        KEY_CREATED_AT: created_at or now,
        KEY_UPDATED_AT: updated_at or now,
    }
    if size is not None:
        doc[KEY_SIZE] = size
    if content_hash is not None and str(content_hash).strip():
        doc[KEY_CONTENT_HASH] = content_hash.strip()
    if md5 is not None and str(md5).strip():
        doc[KEY_MD5] = md5.strip()
    if phash is not None and str(phash).strip():
        doc[KEY_PHASH] = phash.strip()
    if mime_type is not None and str(mime_type).strip():
        doc[KEY_MIME_TYPE] = mime_type.strip()
    if ticket_id is not None and str(ticket_id).strip():
        doc[KEY_TICKET_ID] = ticket_id.strip()
    if task_id is not None and str(task_id).strip():
        doc[KEY_TASK_ID] = task_id.strip()
    if status is not None and str(status).strip():
        doc[KEY_STATUS] = status.strip()
    if metadata is not None and isinstance(metadata, dict):
        doc[KEY_METADATA] = metadata
    return doc


def make_filter(storage_subdir: str, relative_path: str) -> dict[str, Any]:
    """按存储子目录 + 相对路径构造查询条件。"""
    return {
        KEY_STORAGE_SUBDIR: storage_subdir.strip(),
        KEY_RELATIVE_PATH: relative_path.replace("\\", "/").strip().lstrip("/"),
    }


class MetadataStore:
    """
    元数据存储：基于 METADATA_COLLECTION 的增删改查封装。
    通过连接参数在内部实例化 MongoRepositoryBase。
    """

    def __init__(
        self,
        *,
        mongodb_url: str,
        auth_source: Optional[str] = None,
    ) -> None:
        if not mongodb_url or not str(mongodb_url).strip():
            raise ValueError("mongodb_url 不能为空")
        self._repo = MongoRepositoryBase(
            mongodb_url=mongodb_url.strip(),
            db_name=DATABASE_NAME,
            collection_name=METADATA_COLLECTION,
            auth_source=auth_source,
        )
        self._coll = self._repo.collection

    @property
    def collection(self) -> Collection:
        """底层集合。"""
        return self._coll

    def save(
        self,
        *,
        storage_subdir: str,
        relative_path: str,
        size: Optional[int] = None,
        content_hash: Optional[str] = None,
        md5: Optional[str] = None,
        phash: Optional[str] = None,
        mime_type: Optional[str] = None,
        ticket_id: Optional[str] = None,
        task_id: Optional[str] = None,
        status: Optional[str] = None,
        metadata: Optional[dict[str, Any]] = None,
    ) -> Union[InsertOneResult, UpdateResult]:
        """
        存储一条元数据。若 (storage_subdir, relative_path) 已存在则更新，否则插入。
        更新时自动刷新 updated_at，插入时设置 created_at / updated_at。
        """
        doc = make_document(
            storage_subdir=storage_subdir,
            relative_path=relative_path,
            size=size,
            content_hash=content_hash,
            md5=md5,
            phash=phash,
            mime_type=mime_type,
            ticket_id=ticket_id,
            task_id=task_id,
            status=status,
            metadata=metadata,
        )
        flt = make_filter(storage_subdir, relative_path)
        existing = self._coll.find_one(flt)
        if existing:
            update_doc = {
                "$set": {
                    KEY_UPDATED_AT: _now_utc(),
                    **{k: v for k, v in doc.items() if k not in (KEY_CREATED_AT,)},
                }
            }
            return self._coll.update_one(flt, update_doc)
        return self._coll.insert_one(doc)

    def update_by_path(
        self,
        storage_subdir: str,
        relative_path: str,
        *,
        size_bytes: Optional[int] = None,
        content_hash: Optional[str] = None,
        md5: Optional[str] = None,
        phash: Optional[str] = None,
        mime_type: Optional[str] = None,
        ticket_id: Optional[str] = None,
        task_id: Optional[str] = None,
        status: Optional[str] = None,
        metadata: Optional[dict[str, Any]] = None,
    ) -> UpdateResult:
        """
        按 (storage_subdir, relative_path) 更新部分字段。
        """
        flt = make_filter(storage_subdir, relative_path)
        set_payload: dict[str, Any] = {KEY_UPDATED_AT: _now_utc()}
        if size_bytes is not None:
            set_payload[KEY_SIZE] = size_bytes
        if content_hash is not None:
            set_payload[KEY_CONTENT_HASH] = content_hash.strip() if content_hash else None
        if md5 is not None:
            set_payload[KEY_MD5] = md5.strip() if md5 else None
        if phash is not None:
            set_payload[KEY_PHASH] = phash.strip() if phash else None
        if mime_type is not None:
            set_payload[KEY_MIME_TYPE] = mime_type.strip() if mime_type else None
        if ticket_id is not None:
            set_payload[KEY_TICKET_ID] = ticket_id.strip() if ticket_id else None
        if task_id is not None:
            set_payload[KEY_TASK_ID] = task_id.strip() if task_id else None
        if status is not None:
            set_payload[KEY_STATUS] = status.strip() if status else None
        if metadata is not None:
            set_payload[KEY_METADATA] = metadata
        return self._coll.update_one(flt, {"$set": set_payload})

    def update_one(
        self, filter: dict[str, Any], update: dict[str, Any], **kwargs: Any
    ) -> UpdateResult:
        """透传底层 update_one，便于自定义更新。"""
        return self._coll.update_one(filter, update, **kwargs)

    def find_by_path(
        self, storage_subdir: str, relative_path: str
    ) -> Optional[dict[str, Any]]:
        """按 (storage_subdir, relative_path) 查询单条元数据。"""
        return self._coll.find_one(make_filter(storage_subdir, relative_path))

    def list_by_storage_subdir(
        self, storage_subdir: str, **kwargs: Any
    ) -> Cursor[dict[str, Any]]:
        """按 storage_subdir 查询该子目录下所有元数据（返回 Cursor）。"""
        return self._coll.find({KEY_STORAGE_SUBDIR: storage_subdir.strip()}, **kwargs)

    def delete_by_path(
        self, storage_subdir: str, relative_path: str
    ) -> DeleteResult:
        """按 (storage_subdir, relative_path) 删除一条元数据。"""
        return self._coll.delete_one(make_filter(storage_subdir, relative_path))
