"""Step 3 functional services: job detail, revisions, search, and export."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone
from uuid import UUID

from sqlmodel import Session, select

from transcription.db import get_session
from transcription.errors import AppError, ErrorCategory
from transcription.models import Document, Job, JobStatus, Transcript, TranscriptRevision


class LibraryError(AppError):
    """Base error for review/search/export service pathways."""


@dataclass(frozen=True)
class JobDetail:
    """Job detail read model including latest transcript and accepted revision."""

    job: Job
    document: Document | None
    transcript: Transcript | None
    accepted_revision: TranscriptRevision | None


def list_jobs(*, session: Session | None = None) -> list[Job]:
    """Return jobs in most-recent-first order."""
    if session is None:
        with get_session() as local_session:
            return list_jobs(session=local_session)

    return list(session.exec(select(Job).order_by(Job.created_at.desc())).all())


def get_job_detail(*, job_id: UUID, session: Session | None = None) -> JobDetail:
    """Fetch job detail with related document/transcript and accepted revision."""
    if session is None:
        with get_session() as local_session:
            return get_job_detail(job_id=job_id, session=local_session)

    job = session.get(Job, job_id)
    if job is None:
        raise LibraryError(
            f"Job not found: {job_id}",
            category=ErrorCategory.NOT_FOUND,
            suggestion="Refresh jobs list and open a valid job id.",
        )

    document = session.get(Document, job.document_id)
    transcript = session.exec(select(Transcript).where(Transcript.job_id == job.id)).first()
    accepted_revision = session.exec(
        select(TranscriptRevision)
        .where(TranscriptRevision.job_id == job.id, TranscriptRevision.accepted.is_(True))
        .order_by(TranscriptRevision.revision_number.desc())
    ).first()

    return JobDetail(
        job=job,
        document=document,
        transcript=transcript,
        accepted_revision=accepted_revision,
    )


def add_revision(
    *,
    job_id: UUID,
    text: str,
    source: str = "user",
    accepted: bool = False,
    session: Session | None = None,
) -> TranscriptRevision:
    """Append a transcript revision and optionally mark it as accepted."""
    if not text.strip():
        raise LibraryError(
            "Revision text cannot be empty",
            category=ErrorCategory.VALIDATION,
            suggestion="Provide non-empty transcript text and retry.",
        )

    if session is None:
        with get_session() as local_session:
            return add_revision(
                job_id=job_id,
                text=text,
                source=source,
                accepted=accepted,
                session=local_session,
            )

    job = session.get(Job, job_id)
    if job is None:
        raise LibraryError(
            f"Job not found: {job_id}",
            category=ErrorCategory.NOT_FOUND,
            suggestion="Refresh jobs list and retry with a valid job id.",
        )

    revisions = list(
        session.exec(
            select(TranscriptRevision)
            .where(TranscriptRevision.job_id == job_id)
            .order_by(TranscriptRevision.revision_number)
        ).all()
    )
    next_revision_number = (revisions[-1].revision_number + 1) if revisions else 1

    if accepted:
        for existing in revisions:
            if existing.accepted:
                existing.accepted = False
                session.add(existing)

    revision = TranscriptRevision(
        job_id=job_id,
        revision_number=next_revision_number,
        text=text,
        source=source,
        accepted=accepted,
    )
    session.add(revision)

    transcript = session.exec(select(Transcript).where(Transcript.job_id == job_id)).first()
    if transcript is None:
        transcript = Transcript(job_id=job_id)

    transcript.text = text
    transcript.error_detail = None
    session.add(transcript)

    job.updated_at = datetime.now(timezone.utc)
    if accepted:
        job.status = JobStatus.COMPLETED
    elif job.status == JobStatus.QUEUED:
        job.status = JobStatus.TRANSCRIBED
    session.add(job)

    session.commit()
    session.refresh(revision)
    return revision


def accept_revision(*, revision_id: UUID, session: Session | None = None) -> TranscriptRevision:
    """Mark one revision as accepted and synchronize canonical transcript/job state."""
    if session is None:
        with get_session() as local_session:
            return accept_revision(revision_id=revision_id, session=local_session)

    revision = session.get(TranscriptRevision, revision_id)
    if revision is None:
        raise LibraryError(
            f"Revision not found: {revision_id}",
            category=ErrorCategory.NOT_FOUND,
            suggestion="Refresh job detail and select a valid revision.",
        )

    all_revisions = list(session.exec(select(TranscriptRevision).where(TranscriptRevision.job_id == revision.job_id)).all())
    for item in all_revisions:
        item.accepted = item.id == revision.id
        session.add(item)

    transcript = session.exec(select(Transcript).where(Transcript.job_id == revision.job_id)).first()
    if transcript is None:
        transcript = Transcript(job_id=revision.job_id)

    transcript.text = revision.text
    transcript.error_detail = None
    session.add(transcript)

    job = session.get(Job, revision.job_id)
    if job is not None:
        job.status = JobStatus.COMPLETED
        job.updated_at = datetime.now(timezone.utc)
        session.add(job)

    session.commit()
    session.refresh(revision)
    return revision


def list_revisions(*, job_id: UUID, session: Session | None = None) -> list[TranscriptRevision]:
    """Return revision history for a job in ascending revision order."""
    if session is None:
        with get_session() as local_session:
            return list_revisions(job_id=job_id, session=local_session)

    if session.get(Job, job_id) is None:
        raise LibraryError(
            f"Job not found: {job_id}",
            category=ErrorCategory.NOT_FOUND,
            suggestion="Refresh jobs list and open a valid job id.",
        )

    return list(
        session.exec(
            select(TranscriptRevision)
            .where(TranscriptRevision.job_id == job_id)
            .order_by(TranscriptRevision.revision_number)
        ).all()
    )


def search_accepted_transcripts(*, query: str, session: Session | None = None) -> list[TranscriptRevision]:
    """Search accepted transcript revisions using case-insensitive text containment."""
    if not query.strip():
        raise LibraryError(
            "Search query cannot be empty",
            category=ErrorCategory.VALIDATION,
            suggestion="Enter a non-empty search query and retry.",
        )

    if session is None:
        with get_session() as local_session:
            return search_accepted_transcripts(query=query, session=local_session)

    pattern = f"%{query.strip()}%"
    return list(
        session.exec(
            select(TranscriptRevision)
            .where(TranscriptRevision.accepted.is_(True), TranscriptRevision.text.ilike(pattern))
            .order_by(TranscriptRevision.created_at.desc())
        ).all()
    )


def export_transcripts(*, accepted_only: bool = True, session: Session | None = None) -> list[dict[str, str | int | None]]:
    """Export transcript data as serializable records for archive workflows."""
    if session is None:
        with get_session() as local_session:
            return export_transcripts(accepted_only=accepted_only, session=local_session)

    statement = select(TranscriptRevision).order_by(TranscriptRevision.created_at)
    if accepted_only:
        statement = statement.where(TranscriptRevision.accepted.is_(True))

    revisions = list(session.exec(statement).all())
    payload: list[dict[str, str | int | None]] = []
    for revision in revisions:
        detail = get_job_detail(job_id=revision.job_id, session=session)
        payload.append(
            {
                "job_id": str(revision.job_id),
                "document_id": str(detail.job.document_id),
                "filename": detail.document.filename if detail.document else None,
                "revision_id": str(revision.id),
                "revision_number": revision.revision_number,
                "accepted": revision.accepted,
                "source": revision.source,
                "text": revision.text,
                "created_at": revision.created_at.isoformat(),
            }
        )
    return payload
