"""Upload service for storing files and creating queued transcription jobs."""

from __future__ import annotations

import logging
from dataclasses import dataclass
from pathlib import Path
from uuid import UUID, uuid4

from sqlmodel import Session

from transcription.config import Settings, get_settings
from transcription.db import get_session
from transcription.errors import AppError, ErrorCategory
from transcription.models import Document, Job, JobStatus

logger = logging.getLogger(__name__)

SUPPORTED_UPLOAD_EXTENSIONS = {".jpg", ".jpeg", ".png", ".tif", ".tiff", ".pdf"}


class UploadError(AppError):
    """Raised when uploaded content cannot be persisted safely."""


@dataclass(frozen=True)
class UploadJobResult:
    """Summary of created upload records."""

    document_id: UUID
    job_id: UUID
    stored_path: Path
    original_filename: str


def create_upload_job(
    *,
    filename: str,
    file_bytes: bytes,
    session: Session | None = None,
    settings: Settings | None = None,
) -> UploadJobResult:
    """Persist an uploaded file and create document/job records."""
    runtime_settings = settings or get_settings()
    _validate_upload(filename=filename, file_bytes=file_bytes)

    upload_dir = runtime_settings.upload_dir
    upload_dir.mkdir(parents=True, exist_ok=True)

    stored_name = _build_stored_filename(filename)
    stored_path = upload_dir / stored_name

    try:
        stored_path.write_bytes(file_bytes)
    except OSError as exc:
        raise UploadError(
            "Failed to persist upload file",
            category=ErrorCategory.INFRA_PERSISTENT,
            suggestion="Check upload directory permissions and available disk space, then retry.",
        ) from exc

    try:
        if session is not None:
            document, job = _create_upload_records(session=session, original_filename=filename, stored_path=stored_path)
        else:
            with get_session() as local_session:
                document, job = _create_upload_records(
                    session=local_session,
                    original_filename=filename,
                    stored_path=stored_path,
                )
    except Exception as exc:  # noqa: BLE001
        _best_effort_delete(stored_path)
        raise UploadError(
            "Failed to create upload database records",
            category=ErrorCategory.INFRA_TRANSIENT,
            suggestion="Retry upload. If this keeps happening, verify database availability.",
            retriable=True,
        ) from exc

    logger.info("Created upload job document_id=%s job_id=%s", document.id, job.id)
    return UploadJobResult(
        document_id=document.id,
        job_id=job.id,
        stored_path=stored_path,
        original_filename=Path(filename).name,
    )


def _validate_upload(*, filename: str, file_bytes: bytes) -> None:
    if not file_bytes:
        raise UploadError(
            "Upload payload is empty",
            category=ErrorCategory.VALIDATION,
            suggestion="Select a non-empty file and try again.",
        )

    safe_name = Path(filename).name
    if not safe_name:
        raise UploadError(
            "Upload filename is required",
            category=ErrorCategory.VALIDATION,
            suggestion="Choose a file with a valid filename and retry.",
        )

    suffix = Path(safe_name).suffix.lower()
    if suffix not in SUPPORTED_UPLOAD_EXTENSIONS:
        raise UploadError(
            f"Unsupported upload extension: {suffix}",
            category=ErrorCategory.USER_INPUT,
            suggestion="Upload JPG, JPEG, PNG, TIFF, or PDF files only.",
        )


def _build_stored_filename(filename: str) -> str:
    safe_name = Path(filename).name
    return f"{uuid4()}_{safe_name}"


def _create_upload_records(*, session: Session, original_filename: str, stored_path: Path) -> tuple[Document, Job]:
    document = Document(
        filename=Path(original_filename).name,
        file_path=str(stored_path),
    )
    session.add(document)
    session.flush()

    job = Job(
        document_id=document.id,
        status=JobStatus.QUEUED,
    )
    session.add(job)
    session.commit()
    session.refresh(document)
    session.refresh(job)
    return document, job


def _best_effort_delete(path: Path) -> None:
    try:
        if path.exists():
            path.unlink()
    except OSError:
        logger.warning("Failed to clean up upload file after DB error: %s", path)