"""S3 优先 + COS 回退的数据源

对于每个文件：
1. 尝试从 S3（内网 RustFS）stat_object 获取 etag，与本地比对
2. etag 变化则 get_object 全量下载，原子写入本地
3. S3 侧任何异常（网络不通、文件不存在、服务异常）静默 fallback 到 COS
"""

import os
from pathlib import Path

from minio import Minio
from minio.error import S3Error

from ldnode import logger
from ldnode.loader.schemas.enumerations import SyncStatus
from ldnode.loader.schemas.models import DataSource, SyncMetadata, SyncResult
from .cos import COSDataSource


class S3WithCOSFallbackDataSource(DataSource):
    """S3 优先 + COS 回退的数据源"""

    def __init__(
        self,
        minio_client: Minio,
        bucket: str,
        cos_datasource: COSDataSource,
        metadata: SyncMetadata,
        local_data_dir: Path,
    ):
        self._client = minio_client
        self._bucket = bucket
        self._cos = cos_datasource
        self._metadata = metadata
        self._local_dir = local_data_dir
        self._local_dir.mkdir(parents=True, exist_ok=True)

    def _try_s3_load(self, key: str) -> list[SyncResult] | None:
        """尝试从 S3 加载，成功返回 SyncResult 列表，失败返回 None（触发 fallback）"""
        results: list[SyncResult] = []
        try:
            # 先尝试 stat_object 精确匹配（部分 MinIO 版本 list_objects 对精确文件名 prefix 返回空）
            try:
                self._client.stat_object(self._bucket, key)
                objects = [type("Obj", (), {"object_name": key})]
            except S3Error:
                # stat 失败则 fallback 到 list_objects（兼容目录型 key 如 "dim/"）
                objects = list(self._client.list_objects(self._bucket, prefix=key, recursive=True))
            if not objects:
                return None  # S3 上没有这个 key，fallback
        except Exception as e:
            logger.loader.info(f"S3 list_objects 失败 ({key})，fallback COS: {e}")
            return None

        for obj in objects:
            filename = obj.object_name
            local_path = self._local_dir / filename
            try:
                # stat 获取 etag
                stat = self._client.stat_object(self._bucket, filename)
                s3_etag = stat.etag
                local_etag = self._metadata.get_file_etag(filename)

                if local_path.exists() and local_etag == s3_etag:
                    results.append(SyncResult(filename, SyncStatus.NO_CHANGE, s3_etag))
                    continue

                # etag 变化或文件不存在，全量下载
                logger.loader.info(f"S3 同步: {filename} etag {local_etag} → {s3_etag}")
                resp = self._client.get_object(self._bucket, filename)
                try:
                    data = resp.read()
                finally:
                    resp.close()
                    resp.release_conn()

                # 确保目标目录存在
                local_path.parent.mkdir(parents=True, exist_ok=True)
                # 原子写
                tmp_path = local_path.with_suffix(".tmp")
                tmp_path.write_bytes(data)
                os.rename(tmp_path, local_path)
                # 更新 etag
                self._metadata.set_file_etag(filename, s3_etag)
                logger.loader.info(f"S3 同步完成: {filename} size={len(data):,}")
                results.append(SyncResult(filename, SyncStatus.SUCCESS, s3_etag))

            except Exception as e:
                logger.loader.warning(f"S3 下载失败 ({filename})，fallback COS: {e}")
                return None  # 任一文件失败则整体 fallback

        return results

    def load(self, key: str) -> list[SyncResult]:
        """优先 S3，失败 fallback COS"""
        s3_results = self._try_s3_load(key)
        if s3_results is not None:
            return s3_results
        # fallback 到 COS
        return self._cos.load(key)
