import json
import logging
import re

from nicegui import run, ui
from nicegui.events import (
    ValueChangeEventArguments,
)
from xplan_tools.model import model_factory

from xmas_app.components.buttons import FeatureInteractionButton
from xmas_app.settings import get_settings
from xmas_app.util.db import (
    get_nodes,
)

logger = logging.getLogger("xmas_app")


class PlanTree(ui.column):
    def __init__(self, plan_id: str) -> None:
        super().__init__()
        self.plan_id = plan_id
        ui.on("featureAdded", self._update_nodes)

    async def render(self):
        self.action_dialog = ui.dialog().on("hide", lambda e: e.sender.clear())

        tree_filter = (
            ui.input("Baum filtern")
            .props("clearable square filled dense")
            .classes("w-full")
        )
        with tree_filter.add_slot("after"):
            ui.button(icon="refresh", on_click=self._update_nodes).tooltip(
                "Baum aktualisieren"
            )
        ui.separator()

        self.spinner_box = ui.row().classes("w-full justify-center items-center")
        with self.spinner_box:
            ui.spinner(size="xl")

        result = await run.io_bound(get_nodes, self.plan_id)
        if result:
            nodes, featuretype, id, appschema = result
        else:
            return
        self.nodes = nodes

        self.tree = ui.tree(
            self.nodes,
            on_select=self._show_menu,
            tick_strategy=None,
        ).props("accordion no-transition")

        tree_filter.bind_value_to(self.tree, "filter")

        if appschema == "xplan":
            self.tree.expand([id, f"{featuretype}.{id}.bereich"])

        self.spinner_box.clear()

    async def _update_nodes(self):
        result = await run.io_bound(get_nodes, self.plan_id)
        if result:
            nodes, *_ = result
            self.nodes[:] = nodes
            self.tree.update()

    async def _show_menu(self, e: ValueChangeEventArguments):
        if e.value:
            path = e.value.split(".")
            if len(path) == 5:
                _, origin, _, target_featuretype, target = path
                feature = get_settings().repo.get(target)
                with self.action_dialog, ui.card():
                    ui.label(f"{target_featuretype} {target[:8]}").classes("text-bold")
                    FeatureInteractionButton(
                        "highlight_feature",
                        target,
                        geom=feature.get_geom_wkt() is not None,
                    )
                    FeatureInteractionButton("select_feature", target)
                    FeatureInteractionButton("show_attribute_form", target)
                    if not re.match("^.._Bereich$", target_featuretype):
                        ui.button(
                            "Feature löschen",
                            on_click=lambda _, target=target: self._on_delete(target),
                            icon="delete",
                        )
            elif len(path) == 3:
                origin_featuretype, origin_id, rel = path
                with self.action_dialog, ui.card(align_items="stretch"):
                    ui.label(origin_featuretype + " " + origin_id[:8]).classes(
                        "text-bold"
                    )
                    if rel == "bereich":
                        ui.label(f"Keine Aktionen für Referenz {repr(rel)} verfügbar")
                    else:
                        ui.label(f"Neues Feature für Referenz {repr(rel)} erstellen")
                        feature = get_settings().repo.get(origin_id)
                        prop_info = feature.get_property_info(rel)
                        typename = prop_info["typename"]
                        targets = typename if isinstance(typename, list) else [typename]
                        target_select = ui.select(
                            targets,
                            label="Featuretype auswählen",
                            value=None,
                            with_input=True,
                        )
                        if len(targets) == 1:
                            target_select.props("readonly")
                        with target_select.add_slot("append"):
                            ui.badge(str(len(targets))).props("floating")
                        with target_select.add_slot("after"):
                            dropdown = ui.dropdown_button(
                                icon="play_circle",
                                split=True,
                            )
                            target_select.on_value_change(
                                lambda e,
                                origin_featuretype=origin_featuretype,
                                origin_id=origin_id,
                                rel=rel: (
                                    self._prepare_add_feature(
                                        dropdown,
                                        origin_featuretype,
                                        origin_id,
                                        rel,
                                        e.value,
                                    )
                                )
                            )
                            if len(targets) == 1:
                                target_select.set_value(targets[0])
                            else:
                                dropdown.props("disable")

            else:
                return
            self.action_dialog.open()

    async def _prepare_add_feature(
        self,
        dropdown: ui.dropdown_button,
        origin_featuretype: str,
        origin_id: str,
        rel: str,
        target_featuretype: str,
    ):
        async def trigger_add_feature(geometry_type):
            rel_inv = origin_feature.get_property_info(rel)["assoc_info"]["reverse"]
            data = {
                "origin_featuretype": origin_featuretype,
                "origin_id": origin_id,
                "rel": rel,
                "rel_list": origin_feature.get_property_info(rel)["list"],
                "rel_inv": rel_inv,
                "rel_inv_list": target_model.get_property_info(rel_inv)["list"]
                if rel_inv
                else False,
                "target_featuretype": target_featuretype,
                "target_geometrytype": geometry_type,
            }
            logger.info("Sending add_feature to QWebChannel handler")
            payload_json = json.dumps(data)
            await ui.run_javascript(
                f"""
                    (window.ensureXmasHandler ? window.ensureXmasHandler() : Promise.reject('QWebChannel unavailable'))
                        .then(handler => handler.add_feature({payload_json}))
                        .catch(err => console.error('add_feature failed', err));
                """
            )

        GEOMETRY_MAP = {
            "Polygon": {"label": "Fläche", "icon": "o_space_dashboard"},
            "Line": {"label": "Linie", "icon": "show_chart"},
            "Point": {"label": "Punkt", "icon": "o_location_on"},
            "Null": {"label": "Keine Geometrie", "icon": "o_text_snippet"},
        }

        logger.debug("add_feature called")
        dropdown.clear()
        dropdown.props(remove="disable")
        origin_feature = get_settings().repo.get(origin_id)
        target_model = model_factory(
            target_featuretype,
            origin_feature.get_version(),
            origin_feature.get_appschema(),
        )
        geometry_types = []
        if geom_types := target_model.get_geom_types():
            for geom_type in geom_types:
                type_name = geom_type.get_name().replace("Multi", "")
                if type_name not in geometry_types:
                    geometry_types.append(type_name)
        else:
            geometry_types.append("Null")
        if len(geometry_types) == 1:
            dropdown.on_click(lambda: trigger_add_feature(geometry_types[0]))
            dropdown.props("disable-dropdown")
        else:
            with dropdown:
                dropdown.on_click(dropdown.open)
                dropdown.props(remove="disable-dropdown")
                for geometry_type in geometry_types:
                    with ui.item(
                        on_click=lambda geometry_type=geometry_type: (
                            trigger_add_feature(geometry_type)
                        ),
                    ):
                        with ui.item_section().props("avatar"):
                            ui.icon(GEOMETRY_MAP[geometry_type]["icon"])
                        with ui.item_section():
                            ui.item_label(GEOMETRY_MAP[geometry_type]["label"])

    async def _on_delete(self, target_id: str):
        with ui.dialog() as delete_dialog, ui.card(align_items="stretch"):
            ui.label("Soll das Objekt wirklich gelöscht werden?")
            ui.button(
                "Löschen", color="red", on_click=lambda: delete_dialog.submit(True)
            )
            ui.button("Abbrechen", on_click=lambda: delete_dialog.submit(False))

        result = await delete_dialog
        delete_dialog.delete()

        if result:
            try:
                get_settings().repo.delete(target_id)
                ui.notify(
                    f"Feature mit ID '{target_id}' wurde gelöscht", type="positive"
                )
                await self._update_nodes()
            except Exception as e:
                logger.exception("error deleting feature %r: %s", target_id, e)
                ui.notify(
                    f"Fehler beim Löschen vom Feature mit ID '{target_id}'",
                    type="negative",
                )
