"""SQLModel domain models for the V2 transcription system."""

from datetime import UTC
from datetime import date
from datetime import datetime
from enum import StrEnum
from typing import Any
from typing import Optional
from uuid import UUID
from uuid import uuid4

from sqlalchemy import Column
from sqlalchemy import JSON
from sqlalchemy import UniqueConstraint
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm.exc import DetachedInstanceError
from sqlalchemy.types import TypeDecorator
from sqlmodel import Field
from sqlmodel import Relationship
from sqlmodel import SQLModel


class JSONBCompat(TypeDecorator):
    """JSONB for PostgreSQL and JSON for SQLite/testing backends."""

    impl = JSON

    def load_dialect_impl(self, dialect):
        if dialect.name == "postgresql":
            return dialect.type_descriptor(JSONB())
        return dialect.type_descriptor(JSON())


class JobStatus(StrEnum):
    QUEUED = "queued"
    PROCESSING = "processing"
    TRANSCRIBED = "transcribed"
    COMPLETED = "completed"
    PARTIAL_SUCCESS = "partial_success"
    FAILED = "failed"


class DocumentPersonRole(StrEnum):
    AUTHOR = "author"
    RECIPIENT = "recipient"


class JobSourceStatus(StrEnum):
    PENDING = "pending"
    TRANSCRIBED = "transcribed"
    FAILED = "failed"


class Document(SQLModel, table=True):
    """An historical document."""

    id: UUID = Field(default_factory=uuid4, primary_key=True)
    name: str
    document_type: str | None = None
    document_date: date | None = None
    document_date_raw: str | None = None
    location_created: str | None = None
    notes: str | None = None
    archive_identifier: str | None = None
    created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
    updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    jobs: list["Job"] = Relationship(back_populates="document", sa_relationship_kwargs={"lazy": "selectin"})
    sources: list["Source"] = Relationship(back_populates="document", sa_relationship_kwargs={"lazy": "selectin"})
    document_people: list["DocumentPerson"] = Relationship(back_populates="document", sa_relationship_kwargs={"lazy": "selectin"})


class Person(SQLModel, table=True):
    """A historical person linked to one or more documents."""

    id: UUID = Field(default_factory=uuid4, primary_key=True)
    full_name: str
    display_name: str | None = None
    maiden_name: str | None = None
    birth_date: date | None = None
    birth_date_raw: str | None = None
    birth_place: str | None = None
    death_date: date | None = None
    death_date_raw: str | None = None
    death_place: str | None = None
    biography: str | None = None
    portrait_path: str | None = None
    metadata_: dict[str, Any] | None = Field(
        default=None,
        sa_column=Column("metadata", JSONBCompat(), nullable=True),
    )
    created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
    updated_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    document_people: list["DocumentPerson"] = Relationship(back_populates="person", sa_relationship_kwargs={"lazy": "selectin"})


class DocumentPerson(SQLModel, table=True):
    """Associates documents with people in a given role."""

    __tablename__ = "document_person"

    id: UUID = Field(default_factory=uuid4, primary_key=True)
    document_id: UUID = Field(foreign_key="document.id")
    person_id: UUID = Field(foreign_key="person.id")
    role: DocumentPersonRole = Field(default=DocumentPersonRole.AUTHOR)
    created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    __table_args__ = (
        UniqueConstraint("document_id", "person_id", "role", name="uq_document_person_role"),
    )

    document: Optional["Document"] = Relationship(back_populates="document_people", sa_relationship_kwargs={"lazy": "selectin"})
    person: Optional["Person"] = Relationship(back_populates="document_people", sa_relationship_kwargs={"lazy": "selectin"})


class Job(SQLModel, table=True):
    """A transcription job tied to a single document."""

    id: UUID = Field(default_factory=uuid4, primary_key=True)
    document_id: UUID = Field(foreign_key="document.id")
    status: JobStatus = Field(default=JobStatus.QUEUED)
    retry_count: int = Field(default=0, ge=0)
    date_created: datetime = Field(default_factory=lambda: datetime.now(UTC))
    date_updated: datetime = Field(default_factory=lambda: datetime.now(UTC))
    provider: str | None = None
    model: str | None = None
    prompt_name: str | None = None

    document: Optional["Document"] = Relationship(back_populates="jobs", sa_relationship_kwargs={"lazy": "selectin"})
    job_sources: list["JobSource"] = Relationship(back_populates="job", sa_relationship_kwargs={"lazy": "selectin"})

    @property
    def filename(self) -> str:
        """Return the filename of the associated source, when available."""
        if not self.job_sources:
            return "unknown"

        for job_source in self.job_sources:
            source = job_source.__dict__.get("source")
            if source is None:
                try:
                    source = job_source.source
                except DetachedInstanceError:
                    source = None
                except Exception:  # noqa: BLE001
                    source = None

            if source is not None:
                return source.filename

        return "unknown"

    @property
    def error_detail(self) -> str | None:
        """Return the first available source-level error detail for the job."""
        if not self.job_sources:
            return None

        for job_source in self.job_sources:
            if job_source.error_detail:
                return job_source.error_detail

        return None


class Source(SQLModel, table=True):
    """A document source image or PDF page."""

    id: UUID = Field(default_factory=uuid4, primary_key=True)
    document_id: UUID = Field(foreign_key="document.id")
    page_number: int = Field(default=1, ge=1)
    upload_name: str
    filename: str
    file_path: str
    raw_transcription: str | None = None
    revised_text: str | None = None
    date_uploaded: datetime = Field(default_factory=lambda: datetime.now(UTC))
    date_revised: datetime | None = None

    document: Optional["Document"] = Relationship(
        back_populates="sources",
        sa_relationship_kwargs={"lazy": "selectin"},
    )
    job_sources: list["JobSource"] = Relationship(
        back_populates="source",
        sa_relationship_kwargs={"lazy": "selectin"},
    )

    @property
    def latest_job_source(self) -> Optional["JobSource"]:
        """Return the most recent job execution record for this source."""
        if not self.job_sources:
            return None
        return max(self.job_sources, key=lambda js: js.executed_at)

    @property
    def latest_status(self) -> JobSourceStatus | None:
        """Return the execution status of the latest job run."""
        latest = self.latest_job_source
        return latest.status if latest else None

    @property
    def latest_error_detail(self) -> str | None:
        """Return the error detail from the latest job run, if present."""
        latest = self.latest_job_source
        return latest.error_detail if latest else None

    @property
    def document_name(self) -> str | None:
        """Return the parent document name if loaded."""
        return self.document.name if self.document else None

class JobSource(SQLModel, table=True):
    """A single AI execution record for one source page."""

    __tablename__ = "job_source"

    id: UUID = Field(default_factory=uuid4, primary_key=True)
    job_id: UUID = Field(foreign_key="job.id")
    source_id: UUID = Field(foreign_key="source.id")
    status: JobSourceStatus = Field(default=JobSourceStatus.PENDING)
    raw_transcription: str | None = None
    ai_metadata: dict[str, Any] | None = Field(default=None, sa_column=Column(JSONBCompat(), nullable=True))
    raw_api_response: dict[str, Any] | None = Field(default=None, sa_column=Column(JSONBCompat(), nullable=True))
    error_detail: str | None = None
    executed_at: datetime = Field(default_factory=lambda: datetime.now(UTC))

    job: Optional["Job"] = Relationship(back_populates="job_sources", sa_relationship_kwargs={"lazy": "selectin"})
    source: Optional["Source"] = Relationship(back_populates="job_sources", sa_relationship_kwargs={"lazy": "selectin"})


