import logging
import oss2
import requests
from typing import Optional, Union
from src.config.settings import (
    OSS_REGION, 
    OSS_BUCKET, 
    ALIBABA_CLOUD_ACCESS_KEY_ID, 
    ALIBABA_CLOUD_ACCESS_KEY_SECRET,
    OSS_ENDPOINT
)

logger = logging.getLogger(__name__)

class OSSClient:
    _bucket = None
    _logged_warning = False

    @classmethod
    def get_bucket(cls) -> Optional[oss2.Bucket]:
        if cls._bucket:
            return cls._bucket
            
        if not all([OSS_BUCKET, ALIBABA_CLOUD_ACCESS_KEY_ID, ALIBABA_CLOUD_ACCESS_KEY_SECRET]):
            if not cls._logged_warning:
                logger.warning("OSS configuration missing. OSS features will be disabled.")
                cls._logged_warning = True
            return None

        try:
            auth = oss2.Auth(ALIBABA_CLOUD_ACCESS_KEY_ID, ALIBABA_CLOUD_ACCESS_KEY_SECRET)
            endpoint = OSS_ENDPOINT or f'https://{OSS_REGION}.aliyuncs.com'
            cls._bucket = oss2.Bucket(auth, endpoint, OSS_BUCKET)
            logger.info(f"OSS Bucket initialized: {OSS_BUCKET}")
            return cls._bucket
        except Exception as e:
            logger.error(f"Failed to initialize OSS bucket: {e}")
            return None

def get_oss_key(path: str) -> str:
    """ 提取 the object key from an OSS URL or path.
    Strips query parameters and domain.
    """
    if not path:
        return path
        
    key = path
    if path.startswith(('http://', 'https://')):
        try:
            from urllib.parse import urlparse
            parsed = urlparse(path)
            
            # Check if it looks like an Aliyun OSS URL
            if 'aliyuncs.com' in parsed.netloc:
                # If it has a bucket in the domain, it's a full URL
                if OSS_BUCKET and f"{OSS_BUCKET}." in parsed.netloc:
                     key = parsed.path.lstrip('/')
            else:
                 # If it's just a path, it's our bucket, extract key
                if OSS_BUCKET and f"{OSS_BUCKET}." in parsed.netloc:
                    key = parsed.path.lstrip('/')
                else:
                    # If it's just a path, it's our bucket, extract key
                    key = parsed.path.lstrip('/')
             # Not our bucket, we might still want to strip params if it's just a path string
        except Exception:
             pass
    else:
        # Check if it looks like a relative path with query params
        if '?' in path:
            key = path.split('?')[0]
        
    return key.lstrip('/')

def sign_oss_url(path: Optional[str], expiration: int = 3600) -> Optional[str]:
    """ 生成 a signed URL for private OSS objects.
    
    Args:
        path: The object key/path in OSS (or full URL)
        expiration: URL expiration time in seconds (default 1 hour)
        
    Returns:
        Signed URL or original path if not applicable
    """
    if not path or not isinstance(path, str):
        return path

    if path.startswith('data:'):
        return path

     # If it's already a full URL with params, strip them to get the key
    # Logic: If the user requests to sign a URL, they likely want a FRESH signature.
    # So we should ignore existing signatures and re-sign.
    
    bucket = OSSClient.get_bucket()
    if not bucket:
        return path

    key = get_oss_key(path)

    try:
        # Slash_safe=True prevents double encoding of slashes
        return bucket.sign_url('GET', key, expiration, slash_safe=True)
    except Exception as e:
        logger.error(f"Error signing OSS URL for {key}: {e}")
        return path

def upload_bytes(key: str, data: bytes) -> bool:
    """ 上传 bytes to OSS.
    
    Args:
        key: Target object key (path)
        data: Byte data to upload
        
    Returns:
        True if successful, False otherwise
    """
    bucket = OSSClient.get_bucket()
    if not bucket:
        logger.error("OSS Bucket not initialized. Cannot upload.")
        return False
        
    try:
        bucket.put_object(key, data)
        return True
    except Exception as e:
        logger.error(f"Failed to upload to OSS ({key}): {e}")
        return False

def upload_file(key: str, file_path: str) -> bool:
    """ 上传 a local file to OSS.
    
    Args:
        key: Target object key (path)
        file_path: Local file path
        
    Returns:
        True if successful, False otherwise
    """
    bucket = OSSClient.get_bucket()
    if not bucket:
        logger.error("OSS Bucket not initialized. Cannot upload.")
        return False
        
    try:
        bucket.put_object_from_file(key, file_path)
        return True
    except Exception as e:
        logger.error(f"Failed to upload file to OSS ({key}): {e}")
        return False

def delete_object(key: str) -> bool:
    """ 删除 an object from OSS.
    
    Args:
        key: Target object key (path)
        
    Returns:
        True if successful, False otherwise
    """
    bucket = OSSClient.get_bucket()
    if not bucket:
        logger.error("OSS Bucket not initialized. Cannot delete.")
        return False
        
    try:
        bucket.delete_object(key)
        return True
    except Exception as e:
        logger.error(f"Failed to delete object from OSS ({key}): {e}")
        return False

def object_exists(key: str) -> bool:
    """
    Check if object exists in OSS.
    
    Args:
        key: Target object key (path)
        
    Returns:
        True if exists, False otherwise
    """
    bucket = OSSClient.get_bucket()
    if not bucket:
        return False
        
    try:
        return bucket.object_exists(key)
    except Exception as e:
        logger.error(f"Failed to check object existence ({key}): {e}")
        return False

def download_and_upload(url: str, key: str) -> Optional[str]:
    """ 下载 from URL and upload to OSS.
    
    Args:
        url: Source URL
        key: Target object key
        
    Returns:
        Signed OSS URL if successful, None otherwise
    """
    bucket = OSSClient.get_bucket()
    if not bucket:
        return None
        
    try:
        response = requests.get(url, timeout=30)
        if response.status_code == 200:
            bucket.put_object(key, response.content)
            # 返回 signed URL
            return sign_oss_url(key)
        return None
    except Exception as e:
        logger.error(f"Failed to transfer {url} to OSS: {e}")
        return None
