"""Documents list and detail page registration."""

from __future__ import annotations

from datetime import date
from uuid import UUID

from fastapi import Request
from fastapi.responses import RedirectResponse
from nicegui import ui

from transcription.db.models import Document, DocumentPerson, DocumentPersonRole
from transcription.errors import ErrorCategory
from transcription.services.documents import (
    DocumentDeleteBlockedError,
    DocumentError,
    DocumentService,
)
from transcription.ui.components.app_shell import render_navigation_header
from transcription.ui.components.cards import archival_card
from transcription.ui.components.data_display import archival_badge, metadata_row
from transcription.ui.components.error_presenter import show_error
from transcription.ui.components.primitives import (
    destructive_button,
    render_empty_state,
    section_header_row,
)
from transcription.ui.components.table.documents import DocumentTableRow, render_documents_table
from transcription.ui.components.viewers import dark_room_viewer
from transcription.ui.theme import page_header

from ...db.session import SessionFactoryDep

CREATE_NEW_PERSON_OPTION = "__create_new_person__"


def register_page() -> None:
    """Register documents list and detail routes."""

    @ui.page("/documents/new")
    async def document_create_page(request: Request, session_factory: SessionFactoryDep) -> None:
        document_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/documents")

        with ui.column().classes("w-full max-w-4xl mx-auto p-4 gap-4"):
            page_header("Create Document", subtitle="Document name is required.")

            people = sorted(await document_service.list_people(), key=lambda item: item.full_name.casefold())
            form = _render_document_form_fields(people=people)

            requested_doc_id = request.query_params.get("document_id")
            return_to = request.query_params.get("return_to")

            async def submit_create() -> None:
                candidate_name = (form["name"].value or "").strip()
                if not candidate_name:
                    ui.notify("Document name is required.", type="warning")
                    return

                parsed_date = _parse_iso_date(form["date"].value)
                if form["date"].value and parsed_date is None:
                    ui.notify("Exact date must use YYYY-MM-DD.", type="warning")
                    return

                candidate = Document(
                    name=candidate_name,
                    document_type=(form["type"].value or "").strip() or None,
                    document_date=parsed_date,
                    document_date_raw=(form["date_raw"].value or "").strip() or None,
                    location_created=(form["location"].value or "").strip() or None,
                    notes=(form["notes"].value or "").strip() or None,
                    archive_identifier=(form["archive"].value or "").strip() or None,
                )

                try:
                    created = await document_service.create_document(candidate)
                except Exception as exc:  # noqa: BLE001
                    show_error(exc, title="Create failed", operation="documents.create")
                    return

                selected_author = (form["author"].value or "").strip()
                if selected_author == CREATE_NEW_PERSON_OPTION:
                    ui.navigate.to("/people/new")
                    return
                if selected_author:
                    parsed_author_id = _parse_uuid(selected_author)
                    if parsed_author_id is None:
                        ui.notify("Selected author is invalid.", type="warning")
                        return

                    try:
                        await document_service.create_document_person(
                            DocumentPerson(
                                document_id=created.id,
                                person_id=parsed_author_id,
                                role=DocumentPersonRole.AUTHOR,
                            )
                        )
                    except Exception as exc:  # noqa: BLE001
                        show_error(exc, title="Author link failed", operation="documents.create.link_author")
                        return

                ui.notify("Document created", type="positive")
                if return_to == "jobs_new":
                    ui.navigate.to(f"/jobs/new?document_id={created.id}")
                    return
                ui.navigate.to(f"/documents/{created.id}")

            with ui.row().classes("w-full items-center gap-2 mt-2"):
                ui.button("Save document", on_click=submit_create, icon="save").classes("ui-btn-primary")
                ui.button("Cancel", on_click=lambda: ui.navigate.to("/documents"), icon="arrow_back").props("flat")

    @ui.page("/documents")
    async def documents_page(session_factory: SessionFactoryDep) -> None:
        document_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/documents")

        with ui.column().classes("w-full max-w-7xl mx-auto p-4 gap-4"):
            with section_header_row():
                page_header("Archival Documents")
                ui.button(
                    "Create new document",
                    on_click=lambda: ui.navigate.to("/documents/new"),
                    icon="note_add",
                ).classes("ui-btn-primary")

            try:
                documents = sorted(
                    await document_service.list_documents(),
                    key=lambda item: item.created_at,
                    reverse=True,
                )
            except Exception as exc:  # noqa: BLE001
                show_error(exc, title="Load failed", operation="documents.list")
                return

            rows = [
                DocumentTableRow(
                    id=doc.id,
                    name=doc.name,
                    document_type=doc.document_type or "",
                    archive_identifier=doc.archive_identifier or "",
                    created_at=doc.created_at.strftime("%b %d, %Y"),
                )
                for doc in documents
            ]
            render_documents_table(rows)

    @ui.page("/documents/{document_id}")
    async def document_detail_page(document_id: str, session_factory: SessionFactoryDep) -> None:
        document_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/documents")

        parsed_doc_id = _parse_uuid(document_id)
        if parsed_doc_id is None:
            ui.label("Invalid document id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            document = await document_service.read_document_detail(document_id=parsed_doc_id)
        except DocumentError:
            ui.label("Document not found").classes("text-h6 ui-text-danger p-4")
            return
        except Exception as exc:  # noqa: BLE001
            show_error(exc, title="Load failed", operation="documents.read")
            return

        with ui.column().classes("w-full max-w-[1800px] mx-auto p-4 gap-4"):
            with section_header_row():
                page_header(document.name, subtitle=f"Type: {document.document_type or 'Unspecified'} | ID: {document.id}")

                with ui.row().classes("items-center gap-2"):
                    ui.button(
                        "Edit Document",
                        on_click=lambda: ui.navigate.to(f"/documents/{document.id}/edit"),
                        icon="edit",
                    ).classes("ui-btn-primary text-xs")
                    destructive_button(
                        "Delete",
                        on_click=lambda: ui.navigate.to(f"/documents/{document.id}/delete"),
                        icon="delete",
                        extra_classes="text-xs",
                    )

            with ui.grid().classes("w-full grid-cols-12 gap-4"):
                _render_bento_viewer_zone(document)
                _render_bento_metadata_zone(document)
                _render_bento_relations_zone(document)

    @ui.page("/documents/{document_id}/jobs")
    async def document_jobs_page(document_id: str, session_factory: SessionFactoryDep) -> None:
        document_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/documents")

        parsed_doc_id = _parse_uuid(document_id)
        if parsed_doc_id is None:
            ui.label("Invalid document id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            document = await document_service.read_document_detail(document_id=parsed_doc_id)
        except DocumentError:
            ui.label("Document not found").classes("text-h6 ui-text-danger p-4")
            return
        except Exception as exc:  # noqa: BLE001
            show_error(exc, title="Load failed", operation="documents.jobs")
            return

        with ui.column().classes("w-full max-w-4xl mx-auto p-4 gap-4"):
            with section_header_row():
                page_header(f"Jobs for {document.name}")
                with ui.row().classes("gap-2"):
                    ui.button("Back to Document", on_click=lambda: ui.navigate.to(f"/documents/{document.id}"), icon="arrow_back").props("flat")
                    ui.button("Create Job", on_click=lambda: ui.navigate.to(f"/jobs/new?document_id={document.id}"), icon="add").classes("ui-btn-primary")

            if not document.jobs:
                with archival_card(extra_classes="p-6 text-center"):
                    render_empty_state("No transcription processing jobs created yet.")
                return

            for job in sorted(document.jobs, key=lambda item: item.date_created, reverse=True):
                with archival_card(extra_classes="p-3"):
                    with ui.row().classes("w-full items-center justify-between"):
                        with ui.row().classes("items-center gap-2"):
                            archival_badge(job.status.value)
                            ui.label(f"Job ID: {job.id}").classes("text-xs font-mono ui-text-primary")
                        ui.button("Open Job", on_click=lambda _=None, jid=job.id: ui.navigate.to(f"/jobs/{jid}"), icon="open_in_new").props("flat dense").classes("text-xs ui-link-primary")

    @ui.page("/documents/{document_id}/sources")
    async def document_sources_page(document_id: str, session_factory: SessionFactoryDep) -> RedirectResponse:
        _ = session_factory
        return RedirectResponse(url=f"/sources?document_id={document_id}")

    @ui.page("/documents/{document_id}/edit")
    async def document_edit_page(document_id: str, session_factory: SessionFactoryDep) -> None:
        document_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/documents")

        parsed_doc_id = _parse_uuid(document_id)
        if parsed_doc_id is None:
            ui.label("Invalid document id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            document = await document_service.read_document_detail(document_id=parsed_doc_id)
        except DocumentError:
            ui.label("Document not found").classes("text-h6 ui-text-danger p-4")
            return
        except Exception as exc:  # noqa: BLE001
            show_error(exc, title="Load failed", operation="documents.edit.read")
            return

        with ui.column().classes("w-full max-w-4xl mx-auto p-4 gap-4"):
            page_header("Edit Document Record", subtitle="Document name and document type are required.")

            people = sorted(await document_service.list_people(), key=lambda item: item.full_name.casefold())
            existing_author = next((link for link in document.document_people if link.role == DocumentPersonRole.AUTHOR), None)
            form = _render_document_form_fields(document=document, people=people, existing_author_id=existing_author.person_id if existing_author else None)

            async def submit_edit() -> None:
                candidate_name = (form["name"].value or "").strip()
                candidate_type = (form["type"].value or "").strip()
                if not candidate_name:
                    ui.notify("Document name is required.", type="warning")
                    return
                if not candidate_type:
                    ui.notify("Document type is required.", type="warning")
                    return

                parsed_date = _parse_iso_date(form["date"].value)
                if form["date"].value and parsed_date is None:
                    ui.notify("Exact date must use YYYY-MM-DD.", type="warning")
                    return

                candidate = Document(
                    id=document.id,
                    name=candidate_name,
                    document_type=candidate_type,
                    document_date=parsed_date,
                    document_date_raw=(form["date_raw"].value or "").strip() or None,
                    location_created=(form["location"].value or "").strip() or None,
                    notes=(form["notes"].value or "").strip() or None,
                    archive_identifier=(form["archive"].value or "").strip() or None,
                    created_at=document.created_at,
                    updated_at=document.updated_at,
                )

                try:
                    await document_service.update_document(candidate)
                except Exception as exc:  # noqa: BLE001
                    show_error(exc, title="Save failed", operation="documents.edit.save")
                    return

                selected_author = (form["author"].value or "").strip()
                if selected_author == CREATE_NEW_PERSON_OPTION:
                    ui.navigate.to("/people/new")
                    return
                
                existing_author_links = [link for link in document.document_people if link.role == DocumentPersonRole.AUTHOR]
                try:
                    if not selected_author:
                        for link in existing_author_links:
                            await document_service.delete_document_person(link)
                    else:
                        selected_author_id = UUID(selected_author)
                        if not any(link.person_id == selected_author_id for link in existing_author_links):
                            for link in existing_author_links:
                                await document_service.delete_document_person(link)
                            await document_service.create_document_person(
                                DocumentPerson(document_id=document.id, person_id=selected_author_id, role=DocumentPersonRole.AUTHOR)
                            )
                except Exception as exc:  # noqa: BLE001
                    show_error(exc, title="Author update failed", operation="documents.edit.link_author")
                    return

                ui.notify("Document updated", type="positive")
                ui.navigate.to(f"/documents/{document.id}")

            with ui.row().classes("w-full items-center gap-2 mt-2"):
                ui.button("Save changes", on_click=submit_edit, icon="save").classes("ui-btn-primary")
                ui.button("Cancel", on_click=lambda: ui.navigate.to(f"/documents/{document.id}"), icon="arrow_back").props("flat")

    @ui.page("/documents/{document_id}/delete")
    async def document_delete_page(document_id: str, session_factory: SessionFactoryDep) -> None:
        document_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/documents")

        parsed_doc_id = _parse_uuid(document_id)
        if parsed_doc_id is None:
            ui.label("Invalid document id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            document = await document_service.read_document_detail(document_id=parsed_doc_id)
        except DocumentError:
            ui.label("Document not found").classes("text-h6 ui-text-danger p-4")
            return
        except Exception as exc:  # noqa: BLE001
            show_error(exc, title="Load failed", operation="documents.delete.read")
            return

        with ui.column().classes("w-full max-w-xl mx-auto p-4 gap-4"):
            page_header("Delete Document")

            with archival_card(extra_classes="gap-2"):
                ui.label(f"Document: {document.name}").classes("text-sm font-semibold ui-text-primary")

                if document.sources or document.jobs:
                    ui.label("Delete is blocked because related records exist.").classes("text-xs ui-text-danger font-bold mt-2")
                    deps = [cat for cat, present in [("Sources", bool(document.sources)), ("Jobs", bool(document.jobs))] if present]
                    ui.label(f"Dependencies present: {', '.join(deps)}").classes("text-xs ui-text-muted")
                    ui.label("Remove related records first, then retry deletion.").classes("text-xs ui-text-muted italic")

                    with ui.row().classes("w-full items-center gap-2 mt-4"):
                        ui.button("Back to Document", on_click=lambda: ui.navigate.to(f"/documents/{document.id}"), icon="arrow_back").classes("ui-btn-primary text-xs")
                        ui.button("Go to Jobs", on_click=lambda: ui.navigate.to("/jobs"), icon="work_history").props("flat text-xs")
                    return

                ui.label("This action permanently deletes the document.").classes("text-xs ui-text-danger font-medium")

            async def submit_delete() -> None:
                try:
                    await document_service.delete_document(document)
                except DocumentDeleteBlockedError as exc:
                    ui.notify(exc.message, type="warning")
                    ui.navigate.to(f"/documents/{document.id}/delete")
                    return
                except DocumentError as exc:
                    if exc.category == ErrorCategory.NOT_FOUND:
                        ui.notify("Document not found.", type="warning")
                        ui.navigate.to("/documents")
                        return
                    show_error(exc, title="Delete failed", operation="documents.delete")
                    return
                except Exception as exc:  # noqa: BLE001
                    show_error(exc, title="Delete failed", operation="documents.delete")
                    return

                ui.notify("Document deleted", type="positive")
                ui.navigate.to("/documents")

            with ui.row().classes("w-full items-center gap-2 mt-2"):
                destructive_button("Delete document permanently", on_click=submit_delete, icon="delete_forever", variant="solid")
                ui.button("Cancel", on_click=lambda: ui.navigate.to(f"/documents/{document.id}"), icon="arrow_back").props("flat")


# --- Helper Sub-Components ---


def _render_document_form_fields(
    *, document: Document | None = None, people: list[Any], existing_author_id: UUID | None = None
) -> dict[str, Any]:
    with archival_card(extra_classes="gap-3"):
        name_input = ui.input(label="Document name", value=document.name if document else "").props("outlined").classes("w-full ui-form-surface")
        type_input = ui.input(label="Document type", value=document.document_type if document and document.document_type else "").props("outlined").classes("w-full ui-form-surface")

        with ui.row().classes("w-full gap-3 grid grid-cols-1 md:grid-cols-2"):
            date_input = ui.input(
                label="Exact date (YYYY-MM-DD)",
                value=document.document_date.isoformat() if document and document.document_date else "",
            ).props('outlined type="date"').classes("ui-form-surface")
            date_raw_input = ui.input(label="Approximate date", value=document.document_date_raw if document and document.document_date_raw else "").props("outlined").classes("ui-form-surface")

        location_input = ui.input(label="Document location", value=document.location_created if document and document.location_created else "").props("outlined").classes("w-full ui-form-surface")
        archive_input = ui.input(label="Archive identifier", value=document.archive_identifier if document and document.archive_identifier else "").props("outlined").classes("w-full ui-form-surface")
        notes_input = ui.textarea(label="Notes", value=document.notes if document and document.notes else "").props("outlined autogrow").classes("w-full ui-form-surface")

        author_options = {"": "No author", CREATE_NEW_PERSON_OPTION: "Create new item"} | {str(p.id): p.full_name for p in people}
        author_select = ui.select(author_options, label="Author (Person)", value=str(existing_author_id) if existing_author_id else "").props("outlined").classes("w-full ui-form-surface")

    return {
        "name": name_input,
        "type": type_input,
        "date": date_input,
        "date_raw": date_raw_input,
        "location": location_input,
        "archive": archive_input,
        "notes": notes_input,
        "author": author_select,
    }


def _render_bento_viewer_zone(document: Document) -> None:
    with ui.column().classes("col-span-12 lg:col-span-5"):
        source_path = document.sources[0].file_path if document.sources else None
        dark_room_viewer(source_path, count_label=f"{len(document.sources)} Source(s) Linked")
        with ui.row().classes("w-full justify-between items-center mt-2"):
            ui.button("View All Sources", on_click=lambda: ui.navigate.to(f"/sources?document_id={document.id}"), icon="description").props("flat dense text-xs").classes("ui-link-primary")
            ui.button("+ Add Source", on_click=lambda: ui.navigate.to(f"/jobs/new?document_id={document.id}"), icon="add").classes("ui-btn-primary text-xs")


def _render_bento_metadata_zone(document: Document) -> None:
    author_link = next((item for item in document.document_people if item.role == DocumentPersonRole.AUTHOR and item.person is not None), None)

    with ui.column().classes("col-span-12 lg:col-span-4 gap-4"):
        with archival_card(title="Archival Metadata"):
            metadata_row("Author:", author_link.person.full_name if author_link and author_link.person else "Not set")
            metadata_row("Exact Date:", document.document_date.isoformat() if document.document_date else "Not set")
            metadata_row("Approx. Date:", document.document_date_raw or "Not set")
            metadata_row("Location Created:", document.location_created or "Not set")
            metadata_row("Archive Identifier:", document.archive_identifier or "Not set")

            with ui.column().classes("w-full mt-2"):
                ui.label("Archival Notes:").classes("ui-text-muted text-xs mb-1")
                ui.label(document.notes or "No notes added.").classes("p-2 ui-note-box text-xs")

        with archival_card(title="System Logistics"):
            ui.label(f"Created: {document.created_at.isoformat()}").classes("text-[11px] ui-text-muted")
            ui.label(f"Updated: {document.updated_at.isoformat()}").classes("text-[11px] ui-text-muted")


def _render_bento_relations_zone(document: Document) -> None:
    with ui.column().classes("col-span-12 lg:col-span-3 gap-4"):
        with archival_card(title="Related People"):
            if not document.document_people:
                render_empty_state("No linked people yet.", italic=True)
            else:
                with ui.column().classes("w-full gap-2"):
                    for link in document.document_people:
                        person_label = link.person.full_name if link.person is not None else "Unknown person"
                        with ui.row().classes("w-full justify-between items-center ui-row-surface p-2"):
                            ui.label(person_label).classes("text-xs font-semibold ui-text-primary")
                            archival_badge(link.role.value)

        with archival_card(title="Pipeline Jobs"):
            with ui.row().classes("w-full justify-between items-center mb-2"):
                ui.label(f"{len(document.jobs)} Active Jobs").classes("text-xs ui-link-primary font-bold")

            with ui.row().classes("w-full gap-2 mt-2"):
                ui.button("View Jobs", on_click=lambda: ui.navigate.to(f"/documents/{document.id}/jobs"), icon="work_history").props("flat dense text-xs").classes("ui-link-primary")
                ui.button("+ Add Job", on_click=lambda: ui.navigate.to(f"/jobs/new?document_id={document.id}"), icon="add").classes("ui-btn-primary text-xs")


def _parse_uuid(value: str | None) -> UUID | None:
    if not value:
        return None
    try:
        return UUID(value)
    except ValueError:
        return None


def _parse_iso_date(value: str | None) -> date | None:
    candidate = (value or "").strip()
    if not candidate:
        return None
    try:
        return date.fromisoformat(candidate)
    except ValueError:
        return None