"""People list and detail page registration."""

from __future__ import annotations

from datetime import date
from urllib.parse import quote
from uuid import UUID, uuid4

from fastapi import Request
from nicegui import ui

from transcription.config import Settings, get_settings
from transcription.db.models import Person
from transcription.errors import ErrorCategory
from transcription.services.documents import DocumentError, DocumentService
from transcription.services.store import UploadError, store_person_portrait
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.people import PersonTableRow, render_people_table
from transcription.ui.components.viewers import dark_room_viewer
from transcription.ui.theme import page_header

from ...db.session import SessionFactoryDep


def register_page() -> None:  # noqa: PLR0915
    """Register people list and CRUD routes."""

    @ui.page("/people")
    async def people_page(session_factory: SessionFactoryDep) -> None:
        people_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/people")

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

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

            rows = [
                PersonTableRow(
                    id=person.id,
                    full_name=person.full_name,
                    display_name=person.display_name or "",
                    maiden_name=person.maiden_name or "",
                    birth_date=person.birth_date.isoformat() if person.birth_date else "",
                )
                for person in people
            ]
            render_people_table(rows)

    @ui.page("/people/new")
    async def person_create_page(request: Request, session_factory: SessionFactoryDep) -> None:
        people_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/people")
        draft_person_id = uuid4()

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

            form = _render_person_form_fields(
                request=request,
                person_id=draft_person_id,
            )

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

                birth_date = _parse_iso_date(form["birth_date"].value)
                death_date = _parse_iso_date(form["death_date"].value)

                candidate = Person(
                    id=draft_person_id,
                    full_name=full_name,
                    display_name=(form["display_name"].value or "").strip() or None,
                    maiden_name=(form["maiden_name"].value or "").strip() or None,
                    birth_date=birth_date,
                    birth_date_raw=(form["birth_date_raw"].value or "").strip() or None,
                    birth_place=(form["birth_place"].value or "").strip() or None,
                    death_date=death_date,
                    death_date_raw=(form["death_date_raw"].value or "").strip() or None,
                    death_place=(form["death_place"].value or "").strip() or None,
                    biography=(form["biography"].value or "").strip() or None,
                    portrait_path=(form["portrait_path"].value or "").strip() or None,
                )

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

                ui.notify("Person created", type="positive")
                ui.navigate.to(f"/people/{created.id}")

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

    @ui.page("/people/{person_id}")
    async def person_detail_page(person_id: str, session_factory: SessionFactoryDep) -> None:
        people_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/people")

        parsed_person_id = _parse_uuid(person_id)
        if parsed_person_id is None:
            ui.label("Invalid person id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            person = await people_service.read_person_detail(parsed_person_id)
        except DocumentError:
            ui.label("Person not found").classes("text-h6 ui-text-danger p-4")
            return
        except Exception as exc:  # noqa: BLE001
            show_error(exc, title="Load failed", operation="people.read")
            return

        with ui.column().classes("w-full max-w-[1800px] mx-auto p-4 gap-4"):
            with section_header_row():
                page_header(person.full_name, subtitle=f"Person ID: {person.id}")

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

            with ui.grid().classes("w-full grid-cols-12 gap-4"):
                _render_person_portrait_zone(person)
                _render_person_biographical_zone(person)
                _render_person_biography_zone(person)

    @ui.page("/people/{person_id}/edit")
    async def person_edit_page(person_id: str, request: Request, session_factory: SessionFactoryDep) -> None:
        people_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/people")

        parsed_person_id = _parse_uuid(person_id)
        if parsed_person_id is None:
            ui.label("Invalid person id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            person = await people_service.read_person_detail(parsed_person_id)
        except DocumentError:
            ui.label("Person not found").classes("text-h6 ui-text-danger p-4")
            return
        except Exception as exc:  # noqa: BLE001
            show_error(exc, title="Load failed", operation="people.edit.read")
            return

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

            form = _render_person_form_fields(
                request=request,
                person=person,
                person_id=person.id,
            )

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

                birth_date = _parse_iso_date(form["birth_date"].value)
                death_date = _parse_iso_date(form["death_date"].value)

                candidate = Person(
                    id=person.id,
                    full_name=full_name,
                    display_name=(form["display_name"].value or "").strip() or None,
                    maiden_name=(form["maiden_name"].value or "").strip() or None,
                    birth_date=birth_date,
                    birth_date_raw=(form["birth_date_raw"].value or "").strip() or None,
                    birth_place=(form["birth_place"].value or "").strip() or None,
                    death_date=death_date,
                    death_date_raw=(form["death_date_raw"].value or "").strip() or None,
                    death_place=(form["death_place"].value or "").strip() or None,
                    biography=(form["biography"].value or "").strip() or None,
                    portrait_path=(form["portrait_path"].value or "").strip() or None,
                    metadata_=person.metadata_,
                    created_at=person.created_at,
                    updated_at=person.updated_at,
                )

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

                ui.notify("Person updated", type="positive")
                ui.navigate.to(f"/people/{person.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"/people/{person.id}"), icon="arrow_back").props("flat")

    @ui.page("/people/{person_id}/delete")
    async def person_delete_page(person_id: str, session_factory: SessionFactoryDep) -> None:
        people_service = DocumentService(session_factory=session_factory)
        render_navigation_header(current_path="/people")

        parsed_person_id = _parse_uuid(person_id)
        if parsed_person_id is None:
            ui.label("Invalid person id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            person = await people_service.read_person_detail(parsed_person_id)
        except DocumentError:
            ui.label("Person not found").classes("text-h6 ui-text-danger p-4")
            return
        except Exception as exc:  # noqa: BLE001
            show_error(exc, title="Load failed", operation="people.delete.read")
            return

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

            with archival_card(extra_classes="gap-2"):
                ui.label(f"Person: {person.full_name}").classes("text-sm font-semibold ui-text-primary")

                if person.document_people:
                    ui.label(
                        f"This will also remove {len(person.document_people)} linked document relationship(s)."
                    ).classes("text-xs ui-text-danger font-bold mt-2")

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

            async def submit_delete() -> None:
                try:
                    await people_service.delete_person(person)
                except DocumentError as exc:
                    if exc.category == ErrorCategory.NOT_FOUND:
                        ui.notify("Person not found.", type="warning")
                        ui.navigate.to("/people")
                        return
                    show_error(exc, title="Delete failed", operation="people.delete")
                    return
                except Exception as exc:  # noqa: BLE001
                    show_error(exc, title="Delete failed", operation="people.delete")
                    return

                ui.notify("Person deleted", type="positive")
                ui.navigate.to("/people")

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


# --- Helper Sub-Components & Form Builders ---


def _render_person_form_fields(
    *,
    request: Request,
    person: Person | None = None,
    person_id: UUID,
) -> dict[str, Any]:
    with archival_card(extra_classes="gap-3"):
        with ui.row().classes("w-full gap-3 grid grid-cols-1 md:grid-cols-3"):
            full_name_input = ui.input(label="Full name", value=person.full_name if person else "").props("outlined").classes("ui-form-surface")
            display_name_input = ui.input(label="Display name", value=person.display_name if person and person.display_name else "").props("outlined").classes("ui-form-surface")
            maiden_name_input = ui.input(label="Maiden name", value=person.maiden_name if person and person.maiden_name else "").props("outlined").classes("ui-form-surface")

        with ui.row().classes("w-full gap-3 grid grid-cols-1 md:grid-cols-3"):
            birth_date_input = ui.input(
                label="Birth date (YYYY-MM-DD)",
                value=person.birth_date.isoformat() if person and person.birth_date else "",
            ).props('outlined type="date"').classes("ui-form-surface")
            birth_date_raw_input = ui.input(label="Birth date (approximate)", value=person.birth_date_raw if person and person.birth_date_raw else "").props("outlined").classes("ui-form-surface")
            birth_place_input = ui.input(label="Birth place", value=person.birth_place if person and person.birth_place else "").props("outlined").classes("ui-form-surface")

        with ui.row().classes("w-full gap-3 grid grid-cols-1 md:grid-cols-3"):
            death_date_input = ui.input(
                label="Death date (YYYY-MM-DD)",
                value=person.death_date.isoformat() if person and person.death_date else "",
            ).props('outlined type="date"').classes("ui-form-surface")
            death_date_raw_input = ui.input(label="Death date (approximate)", value=person.death_date_raw if person and person.death_date_raw else "").props("outlined").classes("ui-form-surface")
            death_place_input = ui.input(label="Death place", value=person.death_place if person and person.death_place else "").props("outlined").classes("ui-form-surface")

        biography_input = ui.textarea(label="Biography", value=person.biography if person and person.biography else "").props("outlined autogrow").classes("w-full ui-form-surface")
        portrait_path_input = ui.input(label="Portrait path", value=person.portrait_path if person and person.portrait_path else "").props("outlined").classes("w-full ui-form-surface")
        
        _bind_portrait_file_picker(
            portrait_path_input,
            settings=_resolve_runtime_settings(request),
            person_id=person_id,
        )

    return {
        "full_name": full_name_input,
        "display_name": display_name_input,
        "maiden_name": maiden_name_input,
        "birth_date": birth_date_input,
        "birth_date_raw": birth_date_raw_input,
        "birth_place": birth_place_input,
        "death_date": death_date_input,
        "death_date_raw": death_date_raw_input,
        "death_place": death_place_input,
        "biography": biography_input,
        "portrait_path": portrait_path_input,
    }


def _render_person_portrait_zone(person: Person) -> None:
    portrait_src = _resolve_portrait_src(person.portrait_path)
    with ui.column().classes("col-span-12 lg:col-span-4"):
        dark_room_viewer(portrait_src, count_label="Portrait Media")


def _render_person_biographical_zone(person: Person) -> None:
    with ui.column().classes("col-span-12 lg:col-span-4 gap-4"):
        with archival_card(title="Biographical Record"):
            metadata_row("Full Name:", person.full_name)
            metadata_row("Display Name:", person.display_name or "Not set")
            metadata_row("Maiden Name:", person.maiden_name or "Not set")
            metadata_row("Birth Date:", person.birth_date.isoformat() if person.birth_date else "Not set")
            metadata_row("Approx. Birth Date:", person.birth_date_raw or "Not set")
            metadata_row("Birth Place:", person.birth_place or "Not set")
            metadata_row("Death Date:", person.death_date.isoformat() if person.death_date else "Not set")
            metadata_row("Approx. Death Date:", person.death_date_raw or "Not set")
            metadata_row("Death Place:", person.death_place or "Not set")

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


def _render_person_biography_zone(person: Person) -> None:
    with ui.column().classes("col-span-12 lg:col-span-4 gap-4"):
        with archival_card(title="Biography"):
            ui.label(person.biography or "No biography recorded.").classes("p-2 ui-note-box text-xs w-full")

        with archival_card(title="Linked Documents"):
            if not person.document_people:
                render_empty_state("No linked documents yet.", italic=True)
                render_empty_state("Link this person from a Document workflow.")
            else:
                with ui.column().classes("w-full gap-2"):
                    for link in person.document_people:
                        doc = link.document
                        if doc is None:
                            continue
                        with ui.row().classes("w-full justify-between items-center ui-row-surface p-2"):
                            with ui.column().classes("gap-0"):
                                ui.label(doc.name).classes("text-xs font-semibold ui-text-primary")
                                ui.label(f"Role: {link.role.value}").classes("text-[10px] ui-text-muted")
                            ui.button(
                                "Open",
                                on_click=lambda _=None, doc_id=doc.id: ui.navigate.to(f"/documents/{doc_id}"),
                                icon="open_in_new",
                            ).props("flat dense text-xs").classes("ui-link-primary")


# --- Utilities & Input Binding Helpers ---


def _bind_portrait_file_picker(portrait_path_input: ui.input, *, settings: Settings, person_id: UUID) -> None:
    async def on_portrait_selected(event) -> None:
        payload = await event.file.read()
        try:
            stored_path = store_person_portrait(
                person_id=person_id,
                filename=event.file.name,
                file_bytes=payload,
                settings=settings,
            )
        except UploadError as exc:
            ui.notify(str(exc), type="negative")
            return
        except Exception:  # noqa: BLE001
            ui.notify("Unable to store portrait image.", type="negative")
            return

        try:
            relative_path = stored_path.resolve().relative_to(settings.upload_dir.resolve()).as_posix()
        except ValueError:
            relative_path = stored_path.name

        portrait_path_input.value = relative_path
        ui.notify("Portrait uploaded.", type="positive")

    ui.upload(
        on_upload=on_portrait_selected,
        auto_upload=True,
        label="Choose portrait file",
    ).props('accept=".jpg,.jpeg,.png,.gif,.webp,.bmp,.tif,.tiff"').classes("w-full")
    portrait_dir = settings.upload_dir / "persons" / str(person_id)
    ui.label(f"Portraits are stored under {portrait_dir}.").classes("text-xs ui-text-muted")


def _resolve_portrait_src(path: str | None) -> str | None:
    candidate = (path or "").strip()
    if not candidate:
        return None

    normalized = candidate.replace("\\", "/")
    lowered = normalized.casefold()
    if lowered.startswith(("http://", "https://", "data:")):
        return normalized
    if normalized.startswith("/"):
        return normalized
    if lowered.startswith("uploads/"):
        return f"/{normalized}"
    return f"/uploads/{quote(normalized)}"


def _resolve_runtime_settings(request: Request) -> Settings:
    app_settings = getattr(request.app.state, "settings", None)
    if isinstance(app_settings, Settings):
        return app_settings
    return get_settings()


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