"""Jobs list and detail page registration."""

from __future__ import annotations

from pathlib import Path
from uuid import UUID

from fastapi import Request
from nicegui import ui

from transcription.db.models import Job, JobSourceStatus, JobStatus
from transcription.db.session import session_scope
from transcription.services.documents import DocumentService
from transcription.services.jobs import (
    JobCancelBlockedError,
    JobDeleteBlockedError,
    JobResubmitBlockedError,
    JobService,
)
from transcription.services.store import create_job_for_document
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.jobs import JobTableRow, render_jobs_table
from transcription.ui.theme import page_header
from transcription.worker import resolve_worker_notifier

from ...db.session import SessionFactoryDep


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

    @ui.page("/jobs")
    async def jobs_page(session_factory: SessionFactoryDep) -> None:
        jobs_service = JobService(session_factory=session_factory)
        render_navigation_header(current_path="/jobs")

        with ui.column().classes("w-full max-w-7xl mx-auto p-4 gap-4"):
            with section_header_row():
                page_header("Transcription Pipeline Jobs")
                with ui.row().classes("items-center gap-2"):
                    ui.button("Create job", on_click=lambda: ui.navigate.to("/jobs/new"), icon="add").classes(
                        "ui-btn-primary"
                    )
                    ui.button("Refresh", on_click=lambda: render_table.refresh(), icon="refresh").props("flat")

            @ui.refreshable
            async def render_table() -> None:
                jobs = [
                    JobTableRow(
                        id=job.id,
                        status=job.status.value,
                        filename=job.filename,
                        retry_count=job.retry_count,
                        date_created=job.date_created.isoformat(),
                        date_updated=job.date_updated.isoformat(),
                    )
                    for job in await jobs_service.list_jobs()
                ]
                render_jobs_table(jobs)

            await render_table()

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

        with ui.column().classes("w-full max-w-4xl mx-auto p-4 gap-4"):
            page_header("Create Processing Job", subtitle="Queue source files for AI transcription and entity processing.")

            documents = await documents_service.list_documents()
            if not documents:
                _render_no_documents_card()
                return

            uploaded_files: list[tuple[str, bytes]] = []

            with archival_card(extra_classes="gap-3"):
                document_options = {str(doc.id): doc.name for doc in documents}
                document_select = ui.select(document_options, label="Target Document").props("outlined").classes("w-full ui-form-surface")
                
                requested_document_id = request.query_params.get("document_id")
                if requested_document_id in document_options:
                    document_select.value = requested_document_id

                with ui.row().classes("w-full gap-3 grid grid-cols-1 md:grid-cols-3"):
                    provider_input = ui.input(label="Provider").props("outlined").classes("ui-form-surface")
                    model_input = ui.input(label="Model").props("outlined").classes("ui-form-surface")
                    prompt_input = ui.input(label="Prompt").props("outlined").classes("ui-form-surface")

            _render_upload_section(uploaded_files)

            async def submit_create() -> None:
                if not document_select.value:
                    ui.notify("Document is required.", type="warning")
                    return
                if not uploaded_files:
                    ui.notify("At least one source file is required.", type="warning")
                    return

                try:
                    document_id = UUID(str(document_select.value))
                except ValueError:
                    ui.notify("Selected document id is invalid.", type="warning")
                    return

                try:
                    async with session_scope(session_factory=session_factory) as session:
                        result = await create_job_for_document(
                            document_id=document_id,
                            uploads=uploaded_files,
                            provider=(provider_input.value or None),
                            model=(model_input.value or None),
                            prompt_name=(prompt_input.value or None),
                            session=session,
                        )
                except Exception as exc:  # noqa: BLE001
                    show_error(exc, title="Create job failed", operation="jobs.create")
                    return

                resolve_worker_notifier(request.app.state).notify()
                ui.notify(f"Created job {result.job_id}", type="positive")
                ui.navigate.to(f"/jobs/{result.job_id}")

            with ui.row().classes("w-full items-center gap-2 mt-2"):
                ui.button("Submit for transcription", on_click=submit_create, icon="play_arrow").classes("ui-btn-primary")
                ui.button("Back to Jobs", on_click=lambda: ui.navigate.to("/jobs"), icon="arrow_back").props("flat")

    @ui.page("/jobs/{job_id}")
    async def job_detail_page(job_id: str, request: Request, session_factory: SessionFactoryDep) -> None:
        jobs_service = JobService(session_factory=session_factory)
        render_navigation_header(current_path="/jobs")

        parsed_job_id = _parse_uuid(job_id)
        if parsed_job_id is None:
            ui.label("Invalid job id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            job = await jobs_service.read_job(job_id=parsed_job_id)
        except ValueError:
            ui.label("Job not found").classes("text-h6 ui-text-danger p-4")
            return

        with ui.column().classes("w-full max-w-4xl mx-auto p-4 gap-4"):
            _render_job_detail_header(job)

            with ui.grid().classes("w-full grid-cols-1 md:grid-cols-2 gap-4"):
                _render_job_logistics(job)
                _render_job_document_links(job)

    @ui.page("/jobs/{job_id}/cancel")
    async def job_cancel_page(job_id: str, request: Request, session_factory: SessionFactoryDep) -> None:
        jobs_service = JobService(session_factory=session_factory)
        render_navigation_header(current_path="/jobs")

        parsed_job_id = _parse_uuid(job_id)
        if parsed_job_id is None:
            ui.label("Invalid job id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            job = await jobs_service.read_job(job_id=parsed_job_id)
        except ValueError:
            ui.label("Job not found").classes("text-h6 ui-text-danger p-4")
            return

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

            with archival_card(extra_classes="gap-2"):
                ui.label(f"Job ID: {job.id}").classes("text-sm font-semibold font-mono ui-text-primary")
                metadata_row("Current Status:", job.status.value)
                ui.label("Cancel stops processing and marks remaining non-transcribed sources as failed.").classes(
                    "text-xs ui-text-muted"
                )

            async def submit_cancel() -> None:
                try:
                    await jobs_service.cancel_job(job_id=job.id)
                except JobCancelBlockedError as exc:
                    ui.notify(exc.message, type="warning")
                    return
                except ValueError:
                    ui.notify("Job not found.", type="warning")
                    ui.navigate.to("/jobs")
                    return
                except Exception as exc:  # noqa: BLE001
                    show_error(exc, title="Cancel job failed", operation="jobs.cancel")
                    return

                resolve_worker_notifier(request.app.state).notify()
                ui.notify("Job cancelled", type="positive")
                ui.navigate.to(f"/jobs/{job.id}")

            with ui.row().classes("w-full items-center gap-2 mt-2"):
                destructive_button("Cancel job", on_click=submit_cancel, icon="stop_circle", variant="solid")
                ui.button("Back to Job", on_click=lambda: ui.navigate.to(f"/jobs/{job.id}"), icon="arrow_back").props("flat")

    @ui.page("/jobs/{job_id}/resubmit")
    async def job_resubmit_page(job_id: str, request: Request, session_factory: SessionFactoryDep) -> None:
        jobs_service = JobService(session_factory=session_factory)
        render_navigation_header(current_path="/jobs")

        parsed_job_id = _parse_uuid(job_id)
        if parsed_job_id is None:
            ui.label("Invalid job id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            job = await jobs_service.read_job(job_id=parsed_job_id)
        except ValueError:
            ui.label("Job not found").classes("text-h6 ui-text-danger p-4")
            return

        non_transcribed_count = sum(1 for js in job.job_sources if js.status != JobSourceStatus.TRANSCRIBED)

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

            with archival_card(extra_classes="gap-2"):
                ui.label(f"Job ID: {job.id}").classes("text-sm font-semibold font-mono ui-text-primary")
                metadata_row("Current Status:", job.status.value)
                metadata_row("Non-Transcribed Sources:", str(non_transcribed_count))
                ui.label(
                    "Resubmit queues all non-transcribed linked sources. New results overwrite prior page-level results."
                ).classes("text-xs ui-text-muted")

            async def submit_resubmit() -> None:
                try:
                    resubmitted_count = await jobs_service.resubmit_non_transcribed_sources(job_id=job.id)
                except JobResubmitBlockedError as exc:
                    ui.notify(exc.message, type="warning")
                    return
                except ValueError:
                    ui.notify("Job not found.", type="warning")
                    ui.navigate.to("/jobs")
                    return
                except Exception as exc:  # noqa: BLE001
                    show_error(exc, title="Resubmit failed", operation="jobs.resubmit")
                    return

                resolve_worker_notifier(request.app.state).notify()
                ui.notify(f"Resubmitted {resubmitted_count} source(s)", type="positive")
                ui.navigate.to(f"/jobs/{job.id}")

            with ui.row().classes("w-full items-center gap-2 mt-2"):
                ui.button("Resubmit now", on_click=submit_resubmit, icon="replay").classes("ui-btn-primary")
                ui.button("Back to Job", on_click=lambda: ui.navigate.to(f"/jobs/{job.id}"), icon="arrow_back").props("flat")

    @ui.page("/jobs/{job_id}/delete")
    async def job_delete_page(job_id: str, session_factory: SessionFactoryDep) -> None:
        jobs_service = JobService(session_factory=session_factory)
        render_navigation_header(current_path="/jobs")

        parsed_job_id = _parse_uuid(job_id)
        if parsed_job_id is None:
            ui.label("Invalid job id").classes("text-h6 ui-text-danger p-4")
            return

        try:
            job = await jobs_service.read_job(job_id=parsed_job_id)
        except ValueError:
            ui.label("Job not found").classes("text-h6 ui-text-danger p-4")
            return

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

            with archival_card(extra_classes="gap-2"):
                ui.label(f"Job ID: {job.id}").classes("text-sm font-semibold font-mono ui-text-primary")

                if job.status == JobStatus.PROCESSING:
                    ui.label("Delete is blocked while the job is processing.").classes("text-xs ui-text-danger font-bold mt-2")
                    ui.label("Wait for processing to complete, then retry delete.").classes("text-xs ui-text-muted italic")
                    with ui.row().classes("w-full items-center gap-2 mt-4"):
                        ui.button("Back to Job", on_click=lambda: ui.navigate.to(f"/jobs/{job.id}"), icon="arrow_back").classes(
                            "ui-btn-primary text-xs"
                        )
                        ui.button("Back to Jobs", on_click=lambda: ui.navigate.to("/jobs"), icon="work_history").props(
                            "flat text-xs"
                        )
                    return

                ui.label("This action permanently deletes the job.").classes("text-xs ui-text-danger font-medium")
                if job.job_sources:
                    ui.label("Related JobSource links will be removed as part of delete.").classes("text-xs ui-text-muted")

            async def submit_delete() -> None:
                try:
                    await jobs_service.delete_job_with_guardrails(job_id=job.id)
                except JobDeleteBlockedError as exc:
                    ui.notify(exc.message, type="warning")
                    return
                except ValueError:
                    ui.notify("Job not found.", type="warning")
                    ui.navigate.to("/jobs")
                    return
                except Exception as exc:  # noqa: BLE001
                    show_error(exc, title="Delete job failed", operation="jobs.delete")
                    return

                ui.notify("Job deleted", type="positive")
                ui.navigate.to("/jobs")

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


# --- Helper Sub-Components ---


def _render_no_documents_card() -> None:
    with archival_card(extra_classes="p-6 text-center"):
        render_empty_state(
            "No documents available. Create a Document before creating a Job.",
            extra_classes="ui-text-danger font-medium mb-4",
        )
        with ui.row().classes("justify-center gap-2"):
            ui.button(
                "Create document",
                on_click=lambda: ui.navigate.to("/documents/new?return_to=jobs_new"),
                icon="note_add",
            ).classes("ui-btn-primary")
            ui.button("Back to Jobs", on_click=lambda: ui.navigate.to("/jobs"), icon="arrow_back").props("flat")


def _render_upload_section(uploaded_files: list[tuple[str, bytes]]) -> None:
    with archival_card(title="Source Files"):
        ui.label(
            "Files are processed alphabetically by original filename. Use leading numbers such as 001, 002, 003 to control order."
        ).classes("text-xs ui-text-muted mb-2")

        @ui.refreshable
        def render_upload_list() -> None:
            if not uploaded_files:
                render_empty_state("No files uploaded yet.", italic=True)
                return

            def remove_file(index: int) -> None:
                if 0 <= index < len(uploaded_files):
                    removed_name, _ = uploaded_files.pop(index)
                    ui.notify(f"Removed {removed_name}", type="info")
                render_upload_list.refresh()

            def clear_files() -> None:
                uploaded_files.clear()
                ui.notify("Cleared queued files", type="info")
                render_upload_list.refresh()

            ordered_uploads = sorted(
                enumerate(uploaded_files),
                key=lambda item: Path(item[1][0]).name.casefold(),
            )

            with ui.column().classes("gap-1 w-full mt-2"):
                for index, (filename, _) in ordered_uploads:
                    with ui.row().classes("w-full items-center justify-between ui-row-surface p-2"):
                        ui.label(Path(filename).name).classes("text-xs font-mono ui-text-primary")
                        ui.button(icon="delete", on_click=lambda idx=index: remove_file(idx)).props(
                            "flat round dense color=negative text-xs"
                        )

                with ui.row().classes("w-full justify-end mt-2"):
                    ui.button("Clear files", on_click=clear_files, icon="clear_all").props("flat dense").classes(
                        "text-xs ui-text-danger"
                    )

        async def on_upload(event) -> None:
            payload = await event.file.read()
            uploaded_files.append((event.file.name, payload))
            ui.notify(f"Added {event.file.name}", type="positive")
            render_upload_list.refresh()

        ui.upload(
            on_upload=on_upload,
            auto_upload=True,
            label="Select source files or a folder",
        ).props('accept=".jpg,.jpeg,.png,.tif,.tiff,.pdf" webkitdirectory directory multiple').classes("w-full")

        render_upload_list()


def _render_job_detail_header(job: Job) -> None:
    with section_header_row(classes="justify-between items-center"):
        page_header(f"Job Record: {job.id}")
        with ui.row().classes("items-center gap-2"):
            archival_badge(job.status.value.upper())

            if job.status in {JobStatus.QUEUED, JobStatus.PROCESSING}:
                destructive_button(
                    "Cancel",
                    on_click=lambda: ui.navigate.to(f"/jobs/{job.id}/cancel"),
                    icon="stop_circle",
                    extra_classes="text-xs",
                )

            if job.status != JobStatus.TRANSCRIBED:
                ui.button("Resubmit", on_click=lambda: ui.navigate.to(f"/jobs/{job.id}/resubmit"), icon="replay").props(
                    "outlined"
                ).classes("text-xs")

            destructive_button(
                "Delete Job",
                on_click=lambda: ui.navigate.to(f"/jobs/{job.id}/delete"),
                icon="delete",
                extra_classes="text-xs",
            )


def _render_job_logistics(job: Job) -> None:
    with archival_card(title="Execution Logistics"):
        metadata_row("Provider:", job.provider or "pending")
        metadata_row("Model:", job.model or "pending")
        metadata_row("Prompt:", job.prompt_name or "pending")
        metadata_row("Retry Count:", str(job.retry_count))
        metadata_row("Last Updated:", job.date_updated.isoformat())


def _render_job_document_links(job: Job) -> None:
    with archival_card(title="Document Links"):
        ui.label("Navigate to related archival records:").classes("text-xs ui-text-muted mb-3")
        with ui.column().classes("w-full gap-2"):
            ui.button(
                "View Linked Document",
                on_click=lambda: ui.navigate.to(f"/documents/{job.document_id}"),
                icon="description",
            ).classes("ui-btn-primary text-xs w-full")
            ui.button(
                "View Linked Sources",
                on_click=lambda: ui.navigate.to(f"/sources?job_id={job.id}"),
                icon="description",
            ).props("flat text-xs").classes("ui-link-primary w-full")


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