"""阿里云 OSS 存储服务"""

import base64
import datetime as dt
import hashlib
import hmac
import json
import logging
import os
from urllib.parse import urlparse

try:
    import oss2
except ImportError:  # pragma: no cover - exercised in minimal local/test environments
    oss2 = None
from app.config import get_settings

logger = logging.getLogger(__name__)


class OSSStorageService:
    """阿里云 OSS 存储服务"""

    def __init__(self):
        settings = get_settings()
        self.bucket_name = settings.OSS_BUCKET_NAME
        self.public_url = f"https://{settings.OSS_BUCKET_NAME}.{settings.OSS_ENDPOINT}"
        self.local_url_prefix = os.getenv("GENERATED_FILES_URL_PREFIX", "/generated-files")
        self.bucket = None

        has_key_id = bool(settings.OSS_ACCESS_KEY_ID)
        has_key_secret = bool(settings.OSS_ACCESS_KEY_SECRET)

        if has_key_id and has_key_secret:
            if oss2 is not None:
                try:
                    auth = oss2.Auth(
                        settings.OSS_ACCESS_KEY_ID,
                        settings.OSS_ACCESS_KEY_SECRET,
                    )
                    self.bucket = oss2.Bucket(
                        auth,
                        settings.OSS_ENDPOINT,
                        settings.OSS_BUCKET_NAME,
                    )
                    logger.info(
                        "OSS initialized: endpoint=%s bucket=%s",
                        settings.OSS_ENDPOINT,
                        settings.OSS_BUCKET_NAME,
                    )
                except Exception as error:
                    logger.error("Failed to initialize OSS bucket client: %s", error)
            else:
                logger.error("oss2 SDK is not installed — OSS operations will fail")
        else:
            missing = []
            if not has_key_id:
                missing.append("OSS_ACCESS_KEY_ID")
            if not has_key_secret:
                missing.append("OSS_ACCESS_KEY_SECRET")
            logger.error("OSS credentials not configured — missing: %s", ", ".join(missing))

        self.access_key_id = settings.OSS_ACCESS_KEY_ID
        self.access_key_secret = settings.OSS_ACCESS_KEY_SECRET
        self.endpoint = settings.OSS_ENDPOINT

    def _is_local_generated_url(self, storage_key_or_url: str) -> bool:
        parsed = urlparse(storage_key_or_url)
        path = parsed.path if parsed.scheme else storage_key_or_url
        return path.startswith(f"{self.local_url_prefix}/")

    def create_direct_upload_post(
        self,
        storage_key: str,
        content_type: str,
        max_size_bytes: int,
        expires_seconds: int,
    ) -> dict[str, object]:
        """创建浏览器直传 OSS 的 PostObject 表单。"""
        if not self.access_key_id or not self.access_key_secret:
            raise RuntimeError("OSS storage is not configured")
        normalized_key = self._normalize_storage_key(storage_key)
        if not normalized_key.startswith("uploads/"):
            raise ValueError("direct upload key must be under uploads/")

        expiration = (dt.datetime.now(dt.timezone.utc) + dt.timedelta(seconds=expires_seconds)).strftime(
            "%Y-%m-%dT%H:%M:%S.000Z"
        )
        policy = {
            "expiration": expiration,
            "conditions": [
                {"bucket": self.bucket_name},
                {"key": normalized_key},
                ["content-length-range", 1, max_size_bytes],
                ["starts-with", "$Content-Type", content_type.split("/", 1)[0] + "/" if "/" in content_type else ""],
            ],
        }
        encoded_policy = base64.b64encode(json.dumps(policy, separators=(",", ":")).encode("utf-8")).decode("ascii")
        signature = base64.b64encode(
            hmac.new(self.access_key_secret.encode("utf-8"), encoded_policy.encode("utf-8"), hashlib.sha1).digest()
        ).decode("ascii")

        return {
            "url": self.public_url,
            "method": "POST",
            "storage_key": normalized_key,
            "expires_at": expiration,
            "fields": {
                "key": normalized_key,
                "OSSAccessKeyId": self.access_key_id,
                "policy": encoded_policy,
                "Signature": signature,
                "success_action_status": "201",
                "Content-Type": content_type,
            },
        }

    def upload_file(
        self,
        file_or_path,
        storage_key: str,
        content_type: str = "application/octet-stream",
    ) -> str:
        """
        上传文件到 OSS

        Args:
            file_or_path: 文件对象或本地文件路径
            storage_key: 存储键（文件路径）
            content_type: 内容类型

        Returns:
            文件公网 URL
        """
        if self.bucket is None:
            raise RuntimeError("OSS storage is not configured")

        try:
            # 如果是字符串路径，从本地文件上传
            if isinstance(file_or_path, str):
                self.bucket.put_object_from_file(storage_key, file_or_path, headers={"Content-Type": content_type})
            else:
                # 文件对象
                file_or_path.seek(0)
                self.bucket.put_object(storage_key, file_or_path, headers={"Content-Type": content_type})

            return f"{self.public_url}/{storage_key}"
        except Exception as e:
            raise Exception(f"Failed to upload file to OSS: {e}")

    def get_url(self, storage_key: str) -> str:
        """获取文件公网 URL"""
        return f"{self.public_url}/{storage_key}"

    def get_download_url(self, storage_key_or_url: str, expires_seconds: int = 3600) -> str:
        """
        获取文件下载签名 URL（用于私有文件）

        Args:
            storage_key_or_url: 存储键或完整 URL
            expires_seconds: 过期时间（秒）

        Returns:
            签名 URL
        """
        if self.bucket is None:
            if self._is_local_generated_url(storage_key_or_url):
                return storage_key_or_url
            return self.get_url(self._normalize_storage_key(storage_key_or_url))

        storage_key = self._normalize_storage_key(storage_key_or_url)
        try:
            return self.bucket.sign_url("GET", storage_key, expires_seconds)
        except Exception:
            return self.get_url(storage_key)

    def _normalize_storage_key(self, storage_key_or_url: str) -> str:
        """从 URL 中提取存储键"""
        if storage_key_or_url.startswith("http://") or storage_key_or_url.startswith("https://"):
            parsed = urlparse(storage_key_or_url)
            # 移除开头的 /
            return parsed.path.lstrip("/")
        return storage_key_or_url.lstrip("/")

    def download_file(self, storage_key: str, local_path: str):
        """
        从 OSS 下载文件到本地

        Args:
            storage_key: 存储键
            local_path: 本地文件路径
        """
        if self.bucket is None:
            raise RuntimeError("OSS storage is not configured")

        try:
            os.makedirs(os.path.dirname(local_path), exist_ok=True)
            self.bucket.get_object_to_file(storage_key, local_path)
        except Exception as e:
            raise Exception(f"Failed to download file from OSS: {e}")

    def delete_file(self, storage_key: str):
        """
        删除 OSS 文件

        Args:
            storage_key: 存储键
        """
        if self.bucket is None:
            return

        try:
            self.bucket.delete_object(storage_key)
        except Exception as error:
            logger.warning(
                "Failed to delete file from OSS",
                extra={"storage_key": storage_key, "error": str(error)},
            )

    def file_exists(self, storage_key: str) -> bool:
        """
        检查文件是否存在

        Args:
            storage_key: 存储键

        Returns:
            是否存在
        """
        if self.bucket is None:
            return False
        return self.bucket.object_exists(storage_key)


_storage_service = None


def get_storage_service() -> OSSStorageService:
    """获取存储服务实例（全局单例）"""
    global _storage_service
    if _storage_service is None:
        _storage_service = OSSStorageService()
    return _storage_service
