import logging

from nicegui import app, events, run, ui
from pydantic import AnyUrl, ValidationError

from xmas_app.settings import get_settings
from xmas_app.util.db import (
    delete_documents,
    get_doc_by_name,
    get_plan_documents,
    save_documents,
)

logger = logging.getLogger("xmas_app")


class DocumentSelect(ui.select):
    def __init__(self, bind_obj: object, key: str, label: str, validation: dict):
        initial_options = []
        if value := getattr(bind_obj, key, None):
            value = self._ensure_file_uri(value)
            initial_options.append(value)
        super().__init__(
            initial_options,
            label=label,
            value=value,
            new_value_mode="add-unique",
            validation=validation,
        )
        self.bind_value(
            bind_obj,
            key,
            backward=self._ensure_file_uri,
            forward=self._ensure_file_uri,
        )
        self.on("popup-show", self._get_select_options)
        self.on(
            "new-value",
            lambda e: self.set_options(
                self.options + [self._ensure_file_uri(e.args[0])]
            ),
        )

    async def _get_select_options(self):
        options = set(self.options)
        docs = await run.io_bound(get_plan_documents, app.storage.client["plan_id"])
        for doc in docs:
            options.add(doc)
        self.set_options(list(options))

    def _ensure_file_uri(self, value: str | None) -> str | None:
        if not value:
            return None
        try:
            AnyUrl(value)
        except ValidationError:
            return f"file:///{value}"
        else:
            return value


class DocumentEditor(ui.column):
    def __init__(
        self,
        *,
        plan_id: str,
    ):
        super().__init__()
        self.plan_id = plan_id

    async def render(self):
        with self:
            self.docs_table = self._create_table()
            ui.upload(
                multiple=True,
                max_total_size=50000000,
                label="Dokumente importieren",
                on_multi_upload=self._on_upload,
            ).props("flat square bordered").classes("w-full")
        await self._update_table()

    def _create_table(self):
        columns = [
            {
                "name": "filename",
                "label": "Dateiname",
                "field": "filename",
                "align": "left",
                "sortable": True,
            },
            {
                "name": "filetype",
                "label": "Dateityp",
                "field": "filetype",
                "align": "left",
                "sortable": True,
            },
        ]
        table = (
            ui.table(
                rows=[],
                columns=columns,
                row_key="pk",
                selection="multiple",
            )
            .props(
                """
                no-data-label='keine Dokumente vorhanden'
                no-results-label='keine passenden Ergebnisse gefunden'
                flat
                square
                dense
                virtual-scroll
                bordered
            """
            )
            .classes("w-full h-full")
        )
        table.on("row-click", self._on_row_click)
        with table.add_slot("top"):
            with ui.row(wrap=False).classes("gap-0 w-full"):
                search = (
                    ui.input("Filter")
                    .bind_value(table, "filter")
                    .tooltip("über alle Spalten nach Ausdruck filtern")
                    .props("stack-label clearable square filled dense")
                    .classes("w-full")
                )
                with search.add_slot("prepend"):
                    ui.icon("filter_list")
                ui.button(
                    icon="delete", on_click=self._on_delete_docs
                ).bind_enabled_from(
                    table,
                    "selected",
                    backward=lambda selected: len(selected) > 0,
                ).tooltip("Selektion aus Datenbank löschen")
        return table

    async def _on_upload(self, event: events.MultiUploadEventArguments):
        self.docs_table.props("loading")
        try:
            data = []
            for index, content in enumerate(event.contents):
                data.append(
                    {
                        "id": self.plan_id,
                        "filename": event.names[index],
                        "filetype": event.types[index],
                        "filedata": content.read(),
                    }
                )
            save_documents(data)
            await self._update_table()
            self._notify_plan_table()
        except Exception as e:
            logger.exception("error uploading documents: %r", e)
            ui.notify("Fehler beim Speichern der Dokumente", type="negative")

    async def _update_table(self):
        try:
            self.docs_table.props("loading")
            docs = await run.io_bound(get_plan_documents, self.plan_id, True)
            self.docs_table.update_rows(docs, clear_selection=False)
            self._update_row_select_label()
        finally:
            self.docs_table.props(remove="loading")

    def _notify_plan_table(self):
        """Notify the plan table that documents have changed."""
        for client in app.clients("/plans"):
            client.run_javascript("emitEvent('planCreated')")

    async def _on_delete_docs(self):
        try:
            for doc in self.docs_table.selected:
                (
                    get_settings().static_file_dir / self.plan_id / doc["filename"]
                ).unlink(missing_ok=True)
            delete_documents([doc["pk"] for doc in self.docs_table.selected])
            await self._update_table()
            self._notify_plan_table()
        except Exception as e:
            logger.exception("error deleting documents: %r", e)
            ui.notify("Fehler beim Löschen der Dokumente", type="negative")

    def _on_download_file(self):
        doc = self.docs_table.selected[0]
        ui.download.content(
            get_doc_by_name(self.plan_id, doc["filename"]).filedata,
            filename=doc["filename"],
            media_type=doc["filetype"],
        )

    def _update_row_select_label(self):
        doc_number = len(self.docs_table.rows)
        self.docs_table.props(
            f":selected-rows-label='(numberOfRows) => `${{ numberOfRows }} von {doc_number} Dokumenten selektiert`'"
        )

    async def _on_row_click(self, event: events.GenericEventArguments):
        try:
            row = event.args[1]
            doc_name = row["filename"]
        except Exception as e:
            logger.exception("error on loading document data from row: %s", e)
            return ui.notify("Fehler beim Abruf der Dokument-Daten", type="negative")
        ui.navigate.to(f"/plan/{self.plan_id}/documents/{doc_name}")
