"""腾讯云 COS 客户端：通过 S3 兼容 API 上传/下载/删除对象"""

import json
import logging
from typing import Any

import aioboto3
from botocore.config import Config

from app.config import settings

logger = logging.getLogger(__name__)

# COS 配置（生产使用腾讯云 COS，开发环境可使用 MinIO 作为备选）
COS_SECRET_ID = settings.COS_SECRET_ID
COS_SECRET_KEY = settings.COS_SECRET_KEY
COS_REGION = settings.COS_REGION
COS_BUCKET = settings.COS_BUCKET

_session = aioboto3.Session()


def _get_client():
    """创建 aioboto3 S3 客户端，通过 COS 地域 endpoint 连接。"""
    return _session.client(
        "s3",
        region_name=COS_REGION,
        endpoint_url=f"https://cos.{COS_REGION}.myqcloud.com",
        aws_access_key_id=COS_SECRET_ID,
        aws_secret_access_key=COS_SECRET_KEY,
        config=Config(signature_version="s3v4"),
    )


async def ensure_bucket() -> bool:
    """确保 COS 桶存在，不存在则创建（生产环境通常已预先建好）。"""
    try:
        async with _get_client() as s3:
            try:
                await s3.head_bucket(Bucket=COS_BUCKET)
                return True
            except s3.exceptions.ClientError as e:
                if e.response["Error"]["Code"] == "404":
                    await s3.create_bucket(Bucket=COS_BUCKET)
                    logger.info("cos_bucket_created bucket=%s region=%s", COS_BUCKET, COS_REGION)
                    return True
                raise
    except Exception as e:
        logger.warning("cos_bucket_check_error bucket=%s %s", COS_BUCKET, e)
        return False


async def upload_json(object_key: str, data: dict) -> bool:
    """上传 JSON 对象到 COS。"""
    try:
        body = json.dumps(data, ensure_ascii=False).encode("utf-8")
        async with _get_client() as s3:
            await s3.put_object(
                Bucket=COS_BUCKET,
                Key=object_key,
                Body=body,
                ContentType="application/json",
            )
        logger.info("cos_upload_ok key=%s size=%d", object_key, len(body))
        return True
    except Exception as e:
        logger.warning("cos_upload_failed key=%s %s", object_key, e)
        return False


async def download_json(object_key: str) -> dict | None:
    """从 COS 下载 JSON 对象，不存在返回 None。"""
    try:
        async with _get_client() as s3:
            resp = await s3.get_object(Bucket=COS_BUCKET, Key=object_key)
            body = await resp["Body"].read()
            return json.loads(body)
    except Exception as e:
        error_code = getattr(e, "response", {}).get("Error", {}).get("Code", "")
        if error_code == "NoSuchKey":
            logger.info("cos_download_not_found key=%s", object_key)
        else:
            logger.warning("cos_download_failed key=%s %s", object_key, e)
        return None


async def delete_object(object_key: str) -> bool:
    """删除 COS 对象。"""
    try:
        async with _get_client() as s3:
            await s3.delete_object(Bucket=COS_BUCKET, Key=object_key)
        logger.info("cos_delete_ok key=%s", object_key)
        return True
    except Exception as e:
        logger.warning("cos_delete_failed key=%s %s", object_key, e)
        return False


async def get_full_text(lit: Any) -> dict | None:
    """获取文献全文：优先从 COS（full_text_path）读取，降级到 PG JSON 列。"""
    if lit.full_text_path:
        result = await download_json(lit.full_text_path)
        if result is not None:
            return result
    return lit.full_text_sections


_FULLTEXT_CACHE_TTL = 3600  # 1 小时


async def cached_get_full_text(pmid: int, lit: Any) -> dict | None:
    """缓存版 get_full_text：Redis key = fulltext:{pmid}，TTL 1 小时。

    高频文献详情页 + reviews 解析页都调它，缓存避免每次请求都走 MinIO。
    Pipeline 回填全文时调用 invalidate_full_text_cache() 失效该 key。
    """
    from app.core.cache import cache

    async def loader():
        return await get_full_text(lit)

    return await cache.get_or_set(f"fulltext:{pmid}", loader, ttl=_FULLTEXT_CACHE_TTL)


async def invalidate_full_text_cache(pmid: int):
    """Pipeline 回填全文后调用，使该文献的 fulltext 缓存失效。"""
    from app.core.cache import cache
    await cache.delete(f"fulltext:{pmid}")


def make_full_text_key(pmid: int) -> str:
    """按 PMID 生成全文 JSON 的对象键。"""
    return f"fulltext/{pmid}.json"
