import secrets
import time
from dataclasses import dataclass

from app.services.storage.spaces_client import delete_object, upload_buffer
from app.utils.logger import get_logger

logger = get_logger(__name__)


@dataclass
class FontUploadResult:
    url: str
    key: str
    size_kb: float


class FontUploadService:
    @staticmethod
    def _validate_font_file(
        data: bytes,
        content_type: str,
        allowed_types: list[str],
        max_size_mb: int,
        context: str,
    ) -> None:
        if not data:
            logger.error("No font data provided", extra={"context": context})
            raise ValueError("Font file is required")

        if content_type not in allowed_types:
            logger.error(
                "Unsupported font type",
                extra={"context": context, "content_type": content_type},
            )
            raise ValueError(f"Unsupported font type: {content_type}")

        size_mb = round(len(data) / 1024 / 1024, 2)
        if size_mb > max_size_mb:
            logger.error(
                "Font file exceeds max size",
                extra={
                    "context": context,
                    "size_mb": size_mb,
                    "max_size_mb": max_size_mb,
                },
            )
            raise ValueError(f"File exceeds {max_size_mb}MB limit")

        logger.debug(
            "Font file validated", extra={"context": context, "size_mb": size_mb}
        )

    @staticmethod
    def _generate_key(entity: str, entity_id: str, ext: str) -> str:
        token = secrets.token_hex(8)
        return f"{entity}/{entity_id}/{int(time.time() * 1000)}-{token}.{ext}"

    @staticmethod
    def _delete_previous_font(
        previous_key: str | None, uploaded_key: str, context: str
    ) -> None:
        if previous_key and previous_key != uploaded_key:
            try:
                logger.info(
                    "Deleting previous font object",
                    extra={"context": context, "previous_key": previous_key},
                )
                delete_object(previous_key)
                logger.debug(
                    "Previous font deleted",
                    extra={"context": context, "previous_key": previous_key},
                )
            except Exception as exc:
                logger.warning(
                    "Failed to delete previous font",
                    extra={
                        "context": context,
                        "previous_key": previous_key,
                        "error": str(exc),
                    },
                )
        else:
            logger.debug(
                "No previous font deletion required",
                extra={"context": context, "previous_key": previous_key},
            )

    @staticmethod
    def upload(
        *,
        entity: str,
        entity_id: str,
        data: bytes,
        content_type: str,
        filename: str = "font.ttf",
        previous_key: str | None = None,
        allowed_types: list[str] | None = None,
        max_size_mb: int = 5,
        acl: str = "public-read",
    ) -> FontUploadResult:
        context = f"{entity}:{entity_id}"
        allowed = allowed_types or ["font/ttf"]

        FontUploadService._validate_font_file(
            data=data,
            content_type=content_type,
            allowed_types=allowed,
            max_size_mb=max_size_mb,
            context=context,
        )

        ext = filename.split(".")[-1].lower() if "." in filename else "ttf"
        key = FontUploadService._generate_key(entity, entity_id, ext)

        logger.info("Uploading font to storage", extra={"context": context, "key": key})
        result = upload_buffer(key, data, content_type, acl)
        logger.info(
            "Font uploaded",
            extra={"context": context, "key": result["key"], "url": result["url"]},
        )

        FontUploadService._delete_previous_font(previous_key, result["key"], context)

        size_kb = round(len(data) / 1024, 2)
        return FontUploadResult(url=result["url"], key=result["key"], size_kb=size_kb)
