"""
Sandai Operator SDK - Storage Providers

抽象存储接口，支持多种存储后端：MinIO、OSS、Fake 等
"""

import asyncio
import uuid
from abc import ABC, abstractmethod
from pathlib import Path
from datetime import datetime
from typing import Optional, Dict, Tuple
import logging

import oss2
import boto3
from botocore.exceptions import ClientError

from .env import get_minio_config, get_oss_config, get_oss_signed_url_expires

logger = logging.getLogger(__name__)


class StorageProvider(ABC):

    def __init__(self, logger: Optional[logging.Logger] = None):
        self.logger = logger or logging.getLogger(__name__)

    @abstractmethod
    async def upload_file(self, local_file_path: str, ephemeral: bool = True) -> str:
        pass

    @abstractmethod
    async def download_file(self, uri: str, local_file_path: str) -> str:
        """
        从存储下载文件到本地路径
        
        Args:
            uri: 存储 URI (如 s3://bucket/key 或 oss://bucket/key)
            local_file_path: 本地文件保存路径
            
        Returns:
            本地文件路径
        """
        pass

    @property
    @abstractmethod
    def storage_type(self) -> str:
        pass


class MinIOStorageProvider(StorageProvider):
    """MinIO (S3 兼容) 存储提供者"""

    def __init__(self, logger: Optional[logging.Logger] = None):
        super().__init__(logger)
        self.config = get_minio_config()
        self._s3_client = boto3.client(
            "s3",
            endpoint_url=f"{'https' if self.config.secure else 'http'}://{self.config.endpoint}",
            aws_access_key_id=self.config.access_key,
            aws_secret_access_key=self.config.secret_key,
            region_name=self.config.region,
        )
        try:
            self._s3_client.head_bucket(Bucket=self.config.bucket_name)
        except ClientError as e:
            if e.response["Error"]["Code"] == "404":
                self._s3_client.create_bucket(Bucket=self.config.bucket_name)
                self.logger.info(f"Created bucket: {self.config.bucket_name}")
            else:
                raise

    @property
    def storage_type(self) -> str:
        return "minio"

    async def upload_file(self, local_file_path: str, ephemeral: bool = True) -> Tuple[str, Dict]:
        file_path = Path(local_file_path)
        if not file_path.exists():
            raise FileNotFoundError(f"Local file not found: {local_file_path}")

        file_size = file_path.stat().st_size
        file_ext = file_path.suffix

        cur_date_str = datetime.now().strftime("%Y-%m-%d")
        object_filename = f"{uuid.uuid4()}{file_ext}"
        object_key = f"{self.config.prefix}/{cur_date_str}/{object_filename}"

        self._s3_client.upload_file(
            local_file_path, self.config.bucket_name, object_key
        )

        protocol = "https" if self.config.secure else "http"
        s3_url = f"{protocol}://{self.config.endpoint}/{self.config.bucket_name}/{object_key}"

        self.logger.info(
            f"Uploaded {file_path.name} ({file_size} bytes) to MinIO: {s3_url}"
        )
        uri = f"s3://{self.config.bucket_name}/{object_key}"
        meta = {
            "size": file_size,
            "external-url": s3_url,
            "vpc-url": s3_url,
            "bucket": self.config.bucket_name,
            "object": object_key,
            "endpoint": f"{protocol}://{self.config.endpoint}",
            "region": self.config.region,
            "ephemeral": ephemeral,
            "type": "s3"
        }
        return uri, meta

    async def download_file(self, uri: str, local_file_path: str) -> str:
        """从 MinIO/S3 下载文件到本地路径"""
        if not uri.startswith("s3://"):
            raise ValueError(f"Invalid S3 URI: {uri}")
        
        # 解析 S3 URI: s3://bucket/key
        uri_parts = uri[5:].split("/", 1)  # 移除 s3:// 前缀
        if len(uri_parts) != 2:
            raise ValueError(f"Invalid S3 URI format: {uri}")
        
        bucket_name, object_key = uri_parts
        
        # 确保本地目录存在
        local_path = Path(local_file_path)
        local_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 下载文件
            self._s3_client.download_file(bucket_name, object_key, local_file_path)
            
            file_size = local_path.stat().st_size
            self.logger.info(
                f"Downloaded {uri} ({file_size} bytes) to {local_file_path}"
            )
            
            return local_file_path
            
        except ClientError as e:
            error_code = e.response.get("Error", {}).get("Code", "Unknown")
            if error_code == "NoSuchKey":
                raise FileNotFoundError(f"Object not found: {uri}")
            elif error_code == "NoSuchBucket":
                raise FileNotFoundError(f"Bucket not found: {bucket_name}")
            else:
                raise RuntimeError(f"Failed to download {uri}: {e}")
        except Exception as e:
            raise RuntimeError(f"Failed to download {uri}: {e}")


class OSSStorageProvider(StorageProvider):
    """阿里云 OSS 存储提供者"""

    def __init__(self, logger: Optional[logging.Logger] = None, signed_url_expires: Optional[int] = None):
        super().__init__(logger)

        self.config = get_oss_config()
        self.signed_url_expires = signed_url_expires or get_oss_signed_url_expires()

        auth = oss2.Auth(self.config.access_key_id, self.config.access_key_secret)
        self._vpc_bucket = oss2.Bucket(auth, self.config.vpc_endpoint, self.config.bucket_name)
        self._external_bucket = oss2.Bucket(auth, self.config.endpoint, self.config.bucket_name)
        self._bucket = self._external_bucket if self.config.io_via == "external" else self._vpc_bucket

    @property
    def storage_type(self) -> str:
        return "oss"

    async def upload_file(self, local_file_path: str, ephemeral: bool = True) -> Tuple[str, Dict]:
        file_path = Path(local_file_path)
        if not file_path.exists():
            raise FileNotFoundError(f"Local file not found: {local_file_path}")

        file_size = file_path.stat().st_size
        file_ext = file_path.suffix

        cur_date_str = datetime.now().strftime("%Y-%m-%d")
        object_filename = f"{uuid.uuid4()}{file_ext}"
        object_key = f"{self.config.prefix}/{cur_date_str}/{object_filename}"
        if ephemeral:
            headers = {
                'x-oss-tagging': 'ephemeral=true'
            }
        else:
            headers = {}
        self._bucket.put_object_from_file(object_key, local_file_path, headers=headers)

        self.logger.info(
            f"Uploaded {file_path.name} ({file_size} bytes) to OSS: {object_key} [ephemeral=true]"
        )
        uri = f"oss://{self.config.bucket_name}/{object_key}"
        external_url = self.generate_signed_url(object_key, self.signed_url_expires, use_vpc=False)
        vpc_url = self.generate_signed_url(object_key, self.signed_url_expires, use_vpc=True)
        meta = {
            "size": file_size,
            "external-url": external_url,
            "vpc-url": vpc_url,
            "bucket": self.config.bucket_name,
            "object": object_key,
            "endpoint": self.config.endpoint,
            "region": self.config.region,
            "ephemeral": ephemeral,
            "type": "oss"
        }
        return uri, meta

    def generate_signed_url(self, object_key: str, expires: Optional[int] = None, use_vpc: bool = False) -> str:
        expires = expires or self.signed_url_expires
        
        if use_vpc:
            return self._vpc_bucket.sign_url('GET', object_key, expires)
        else:
            return self._external_bucket.sign_url('GET', object_key, expires)

    async def download_file(self, uri: str, local_file_path: str) -> str:
        """从 OSS 下载文件到本地路径"""
        if not uri.startswith("oss://"):
            raise ValueError(f"Invalid OSS URI: {uri}")
        
        # 解析 OSS URI: oss://bucket/key
        uri_parts = uri[6:].split("/", 1)  # 移除 oss:// 前缀
        if len(uri_parts) != 2:
            raise ValueError(f"Invalid OSS URI format: {uri}")
        
        bucket_name, object_key = uri_parts
        
        # 确保本地目录存在
        local_path = Path(local_file_path)
        local_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 使用配置的 bucket 下载文件
            self._bucket.get_object_to_file(object_key, local_file_path)
            
            file_size = local_path.stat().st_size
            self.logger.info(
                f"Downloaded {uri} ({file_size} bytes) to {local_file_path}"
            )
            
            return local_file_path
            
        except oss2.exceptions.NoSuchKey:
            raise FileNotFoundError(f"Object not found: {uri}")
        except oss2.exceptions.NoSuchBucket:
            raise FileNotFoundError(f"Bucket not found: {bucket_name}")
        except oss2.exceptions.OssError as e:
            raise RuntimeError(f"Failed to download {uri}: {e}")
        except Exception as e:
            raise RuntimeError(f"Failed to download {uri}: {e}")


class FakeStorageProvider(StorageProvider):
    """假存储提供者（用于开发和测试）"""

    def __init__(self, logger: Optional[logging.Logger] = None):
        super().__init__(logger)
        # 使用 MinIO 配置作为 fake URL 的格式参考
        self.config = get_minio_config()

    @property
    def storage_type(self) -> str:
        return "fake"

    async def upload_file(self, local_file_path: str,  ephemeral: bool = True) -> Tuple[str, Dict]:
        file_path = Path(local_file_path)
        if not file_path.exists():
            raise FileNotFoundError(f"Local file not found: {local_file_path}")
        
        file_size = file_path.stat().st_size
        file_ext = file_path.suffix

        cur_date_str = datetime.now().strftime("%Y-%m-%d")
        object_filename = f"{uuid.uuid4()}{file_ext}"
        upload_delay = min(0.1 + file_size / (10 * 1024 * 1024), 2.0)
        await asyncio.sleep(upload_delay)

        # 生成 Mock URL（使用 MinIO 格式）
        protocol = "https" if self.config.secure else "http"
        mock_url = f"{protocol}://{self.config.endpoint}/{self.config.bucket_name}/{self.config.prefix}/{cur_date_str}/{object_filename}"

        self.logger.info(
            f"Mock uploaded {file_path.name} ({file_size} bytes) to: {mock_url}"
        )
        
        uri = f"fake://{self.config.bucket_name}/{self.config.prefix}/{object_filename}"
        meta = {
            "size": file_size,
            "external-url": mock_url,
            "vpc-url": mock_url,
            "bucket": self.config.bucket_name,
            "object": f"{self.config.prefix}/{cur_date_str}/{object_filename}",
            "endpoint": f"{protocol}://{self.config.endpoint}",
            "region": self.config.region,
            "type": "fake",
            "ephemeral": ephemeral,
        }
        return uri, meta

    async def download_file(self, uri: str, local_file_path: str) -> str:
        """模拟从存储下载文件到本地路径"""
        # 确保本地目录存在
        local_path = Path(local_file_path)
        local_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 模拟下载延迟
        download_delay = 0.1
        await asyncio.sleep(download_delay)
        
        # 创建一个模拟文件
        mock_content = f"Mock file content for {uri}\nGenerated at download time."
        with open(local_file_path, "w", encoding="utf-8") as f:
            f.write(mock_content)
        
        file_size = local_path.stat().st_size
        self.logger.info(
            f"Mock downloaded {uri} ({file_size} bytes) to {local_file_path}"
        )
        
        return local_file_path



def create_storage_provider(
    storage_type: Optional[str] = None, 
    logger: Optional[logging.Logger] = None,
    **kwargs
) -> StorageProvider:
    """
    创建存储提供者

    Args:
        storage_type: 存储类型 ("minio", "oss", "fake", None)
                     如果为 None，则按优先级自动选择
        logger: 日志记录器
        **kwargs: 传递给存储提供者的额外参数
                 - signed_url_expires: OSS 签名 URL 过期时间（秒）

    Returns:
        StorageProvider 实例
    """
    if logger is None:
        logger = logging.getLogger(__name__)
    
    storage_type = storage_type or "fake"

    # 如果指定了存储类型，直接创建
    if storage_type == "minio":
        return MinIOStorageProvider(logger)
    elif storage_type == "oss":
        # 提取 OSS 特定的参数
        oss_kwargs = {}
        if 'signed_url_expires' in kwargs:
            oss_kwargs['signed_url_expires'] = kwargs['signed_url_expires']
        return OSSStorageProvider(logger, **oss_kwargs)
    elif storage_type == "fake":
        return FakeStorageProvider(logger)
    else:
        raise ValueError(f"Unknown storage type: {storage_type}")


def get_storage_type_from_env() -> Optional[str]:
    """
    从环境变量获取存储类型

    环境变量:
        SANDAI_OPERATOR_STORAGE_TYPE: 存储类型 ("minio", "oss", "fake")

    Returns:
        存储类型字符串，如果未设置则返回 None
    """
    import os

    storage_type = os.getenv("SANDAI_OPERATOR_STORAGE_TYPE")
    if storage_type:
        storage_type = storage_type.lower()
        if storage_type in ["minio", "oss", "fake"]:
            return storage_type
        else:
            raise ValueError(
                f"Invalid storage type in SANDAI_OPERATOR_STORAGE_TYPE: {storage_type}"
            )
    return None


def create_default_storage_provider(
    **kwargs
) -> StorageProvider:
    """
    创建默认存储提供者

    优先级：
    1. 环境变量 SANDAI_OPERATOR_STORAGE_TYPE 指定的类型
    2. 自动检测可用的存储（MinIO > OSS > Fake）

    Args:
        logger: 日志记录器
        **kwargs: 传递给存储提供者的额外参数

    Returns:
        StorageProvider 实例
    """
    storage_type = get_storage_type_from_env()
    logger.info(f"Using storage type: {storage_type}")

    # 创建存储提供者
    return create_storage_provider(storage_type, logger, **kwargs)
