import secrets
from datetime import datetime, timedelta, timezone
from pathlib import Path

from app.config import settings

_upload_tokens: dict[str, dict] = {}


def _upload_root() -> Path:
    root = Path(settings.upload_local_dir)
    root.mkdir(parents=True, exist_ok=True)
    return root


def generate_object_key(child_id: int, file_name: str) -> str:
    safe_name = file_name.replace("..", "").replace("/", "_")
    return f"videos/{child_id}/{safe_name}"


def _cleanup_expired_tokens() -> None:
    now = datetime.now(timezone.utc)
    expired = [k for k, v in _upload_tokens.items() if v["expire_at"] < now]
    for key in expired:
        _upload_tokens.pop(key, None)


def create_upload_token(object_key: str, content_type: str, size: int) -> dict:
    _cleanup_expired_tokens()
    expire_at = datetime.now(timezone.utc) + timedelta(minutes=settings.upload_token_expire_minutes)
    token = secrets.token_urlsafe(32)

    if settings.oss_enabled:
        upload_url, method = _create_s3_presigned_url(object_key, content_type, expire_at)
        extra = {
            "storage": settings.oss_provider,
            "cdnBaseUrl": settings.oss_cdn_base_url or None,
        }
    else:
        upload_url = f"{settings.api_public_url.rstrip('/')}/api/videos/direct-upload"
        method = "PUT"
        extra = {"storage": "local", "uploadToken": token}

    _upload_tokens[token] = {
        "object_key": object_key,
        "content_type": content_type,
        "size": size,
        "expire_at": expire_at,
    }

    return {
        "uploadUrl": upload_url,
        "objectKey": object_key,
        "expireAt": expire_at.replace(microsecond=0).isoformat(),
        "method": method,
        **extra,
        "uploadToken": token,
    }


def _create_s3_presigned_url(object_key: str, content_type: str, expire_at: datetime) -> tuple[str, str]:
    try:
        import boto3
        from botocore.client import Config
    except ImportError as exc:
        raise RuntimeError("boto3 required for OSS mode") from exc

    client = boto3.client(
        "s3",
        endpoint_url=settings.oss_endpoint,
        aws_access_key_id=settings.oss_access_key,
        aws_secret_access_key=settings.oss_secret_key,
        config=Config(signature_version="s3v4"),
        region_name=settings.oss_region,
    )
    _ensure_bucket(client)
    expires_in = max(60, int((expire_at - datetime.now(timezone.utc)).total_seconds()))
    url = client.generate_presigned_url(
        "put_object",
        Params={
            "Bucket": settings.oss_bucket,
            "Key": object_key,
            "ContentType": content_type,
        },
        ExpiresIn=expires_in,
    )
    return url, "PUT"


def _ensure_bucket(client) -> None:
    if settings.oss_provider != "minio":
        return
    try:
        client.head_bucket(Bucket=settings.oss_bucket)
    except Exception:
        client.create_bucket(Bucket=settings.oss_bucket)


def public_object_url(object_key: str) -> str | None:
    if settings.oss_cdn_base_url:
        return f"{settings.oss_cdn_base_url.rstrip('/')}/{object_key}"
    if settings.oss_enabled and settings.oss_provider == "aliyun":
        endpoint = settings.oss_endpoint.rstrip("/")
        return f"{endpoint}/{settings.oss_bucket}/{object_key}"
    return None


def validate_upload_token(token: str, object_key: str) -> bool:
    _cleanup_expired_tokens()
    meta = _upload_tokens.get(token)
    if not meta or meta["object_key"] != object_key:
        return False
    if meta["expire_at"] < datetime.now(timezone.utc):
        _upload_tokens.pop(token, None)
        return False
    return True


def consume_upload_token(token: str, object_key: str) -> None:
    meta = _upload_tokens.pop(token, None)
    if not meta or meta["object_key"] != object_key:
        raise ValueError("invalid_upload_token")


def save_local_upload(object_key: str, data: bytes) -> Path:
    path = _upload_root() / object_key
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_bytes(data)
    return path


def resolve_local_path(object_key: str) -> Path | None:
    path = _upload_root() / object_key
    return path if path.exists() else None
