"""
Storage Service — Wraps Supabase Storage operations for Talk2CV.

Uses the admin Supabase client (SECRET_KEY) from app.core.supabase_client.
Upload operations are best-effort: they log and return None on failure
instead of raising, so callers (transcribe, cv generation) can continue
even if Storage is temporarily unavailable.

Signed URL generation raises on failure (callers need to know).

Paths:
- CV PDFs:  {user_id}/cv.pdf       in bucket SUPABASE_CVS_BUCKET
- Audios:   {user_id}/step_{N}.m4a in bucket SUPABASE_AUDIOS_BUCKET
"""

import logging
from typing import Optional

from app.core.config import settings
from app.core.supabase_client import supabase

logger = logging.getLogger(__name__)


class StorageService:
    """Service for uploading files and generating signed URLs via Supabase Storage."""

    def __init__(self) -> None:
        self._cvs_bucket = settings.SUPABASE_CVS_BUCKET
        self._audios_bucket = settings.SUPABASE_AUDIOS_BUCKET
        self._default_ttl = settings.SIGNED_URL_TTL_SECONDS

    def upload_cv_pdf(self, user_id: str, pdf_bytes: bytes) -> Optional[str]:
        """
        Upload a CV PDF to {user_id}/cv.pdf in the cvs bucket.

        Best-effort: returns the storage path on success, None on failure.
        Existing file is overwritten (upsert).

        Args:
            user_id: Supabase auth user UUID.
            pdf_bytes: Raw PDF bytes from WeasyPrint.

        Returns:
            Storage path string (e.g. "abc-123/cv.pdf") on success, None on failure.
        """
        path = f"{user_id}/cv.pdf"
        try:
            supabase.storage.from_(self._cvs_bucket).upload(
                path=path,
                file=pdf_bytes,
                file_options={
                    "content-type": "application/pdf",
                    "upsert": "true",
                },
            )
            logger.info("Uploaded CV PDF (user=%s, bytes=%d)", user_id, len(pdf_bytes))
            return path
        except Exception as exc:
            logger.exception("CV PDF upload failed (user=%s): %s", user_id, exc)
            return None

    def upload_audio(self, user_id: str, step: int, audio_bytes: bytes) -> Optional[str]:
        """
        Upload a step audio recording to {user_id}/step_{N}.m4a in the audios bucket.

        Best-effort: returns the storage path on success, None on failure.
        Existing file is overwritten (upsert). Step must be 1..4.

        Args:
            user_id: Supabase auth user UUID.
            step: VoiceStepper step number (1..4).
            audio_bytes: Raw audio bytes (m4a).

        Returns:
            Storage path string on success, None on failure.
        """
        if not 1 <= step <= 4:
            logger.warning("upload_audio: invalid step=%s (must be 1..4)", step)
            return None

        path = f"{user_id}/step_{step}.m4a"
        try:
            supabase.storage.from_(self._audios_bucket).upload(
                path=path,
                file=audio_bytes,
                file_options={
                    "content-type": "audio/mp4",
                    "upsert": "true",
                },
            )
            logger.info(
                "Uploaded audio (user=%s, step=%d, bytes=%d)",
                user_id, step, len(audio_bytes),
            )
            return path
        except Exception as exc:
            logger.exception(
                "Audio upload failed (user=%s, step=%d): %s",
                user_id, step, exc,
            )
            return None

    def delete_user_files(self, user_id: str) -> None:
        """
        Delete all storage objects belonging to a user: the CV PDF and any
        step audio recordings. Best-effort per bucket — a failure in one
        bucket is logged but does not stop the other from being attempted
        (mirrors the best-effort philosophy of upload_cv_pdf/upload_audio:
        callers get partial cleanup with a log trail rather than a hard
        failure on an already-irreversible user deletion).

        Audio files are listed first rather than assumed present for
        steps 1..4, since a user may not have completed every step.
        """
        try:
            supabase.storage.from_(self._cvs_bucket).remove([f"{user_id}/cv.pdf"])
            logger.info("Deleted CV PDF for user=%s", user_id)
        except Exception as exc:
            logger.exception("Failed to delete CV PDF for user=%s: %s", user_id, exc)

        try:
            listing = supabase.storage.from_(self._audios_bucket).list(path=user_id)
            paths = [f"{user_id}/{item['name']}" for item in (listing or [])]
            if paths:
                supabase.storage.from_(self._audios_bucket).remove(paths)
                logger.info("Deleted %d audio file(s) for user=%s", len(paths), user_id)
            else:
                logger.info("No audio files found for user=%s", user_id)
        except Exception as exc:
            logger.exception("Failed to delete audio files for user=%s: %s", user_id, exc)

    def get_signed_url(
        self,
        bucket: str,
        path: str,
        ttl_seconds: Optional[int] = None,
    ) -> str:
        """
        Generate a signed URL for a private storage object.

        Raises on failure — callers need to know if URL generation fails.

        Args:
            bucket: Bucket name (e.g. settings.SUPABASE_CVS_BUCKET).
            path: Object path within the bucket (e.g. "abc-123/cv.pdf").
            ttl_seconds: URL validity in seconds. Defaults to
                settings.SIGNED_URL_TTL_SECONDS (3600 = 1h).

        Returns:
            Signed URL string.

        Raises:
            RuntimeError: If Supabase returns an unexpected response shape.
            Exception: Propagates any Supabase client error.
        """
        ttl = ttl_seconds if ttl_seconds is not None else self._default_ttl

        response = supabase.storage.from_(bucket).create_signed_url(
            path=path,
            expires_in=ttl,
        )

        # supabase-py returns {"signedURL": "...", "signedUrl": "..."}
        # depending on version. Handle both, then fall back to "signed_url".
        url = (
            response.get("signedURL")
            or response.get("signedUrl")
            or response.get("signed_url")
        )
        if not url:
            raise RuntimeError(
                f"Supabase signed URL response missing url field: {response}"
            )

        logger.info("Signed URL generated (bucket=%s, path=%s, ttl=%ds)", bucket, path, ttl)
        return url


# Module-level singleton — convenient for direct import
storage_service = StorageService()
