import json
import logging

from nicegui import app, run, ui
from xplan_tools.model.base import Appschema
from xplan_tools.model.result.result_report import (
    ErrorIssue,
    TransformationValidationError,
    WarningIssue,
)
from xplan_tools.transform import transformer_factory
from xplan_tools.util import MigrationPath

from xmas_app.components.buttons import FeatureInteractionButton
from xmas_app.settings import get_settings

logger = logging.getLogger("xmas_app")


class PlanMigration(ui.column):
    def __init__(self, plan_id: str) -> None:
        super().__init__()
        self.plan_id = plan_id
        self.classes("w-full h-full")
        self.repo = get_settings().repo

    async def render(self) -> None:
        with self:
            self.action_dialog = ui.dialog().on("hide", lambda e: e.sender.clear())
            self.version_select = ui.select([], label="Zielversion auswählen").classes(
                "w-full"
            )
            with self.version_select.add_slot("after"):
                self.start_button = ui.button(
                    icon="play_circle",
                    on_click=lambda: self._on_migrate(self.version_select.value),
                ).bind_enabled_from(self.version_select, "value")
            ui.separator()
            self.spinner_box = ui.row().classes("w-full justify-center items-center")
            self.issue_list = ui.list()
        await self._get_select_options()

    async def _get_select_options(self) -> None:
        plan = await run.io_bound(self.repo.get, self.plan_id)
        plan_appschema = plan.appschema()
        available_versions = {
            appschema.version: f"{plan_appschema.prefix.upper()} {appschema.version}"
            for appschema in Appschema.supported_appschemas()
            if appschema.prefix == plan_appschema.prefix
            and appschema.full_version > plan_appschema.full_version
        }
        self.version_select.set_options(available_versions, value=None)

    async def _on_migrate(self, version: str) -> None:
        with self.spinner_box:
            ui.spinner(size="xl")
        self.start_button.disable()
        self.issue_list.clear()
        try:
            collection = await run.io_bound(self.repo.get_plan_by_id, self.plan_id)
            feature_ids = set(collection.features.keys())
            path = MigrationPath(collection.version, version).path
            if not path:
                ui.notify("Keinen Migrationspfad gefunden", type="warning")
                return
            if len(path) > 1:
                ui.notify(
                    f"Führe mehrschrittige Versionmigration {' - '.join(path)} aus.",
                )
            result = None
            reports = {}
            for step in path:
                try:
                    result = transformer_factory(collection, step).transform()
                    collection = result.output
                    if step == "6.1":
                        collection = transformer_factory(
                            collection, "6.1"
                        ).set_associations()
                    reports[step] = result.warnings
                except TransformationValidationError as e:
                    logger.exception("error during version migration: %r", e)
                    ui.notify(
                        "Bei der Migration sind Validierungsfehler aufgetreten",
                        type="negative",
                    )
                    return self._show_report({step: e.result.errors})
            if not result:
                raise ValueError("no version path found")
            try:
                if deleted := feature_ids.difference(result.output.features.keys()):
                    logger.info(
                        "the following features are deleted after migration to version %s: %s",
                        version,
                        deleted,
                    )
                    for d in deleted:
                        await run.io_bound(self.repo.delete, d)
                await run.io_bound(self.repo.update_all, result.output)
            except Exception as e:
                logger.exception(
                    "error while saving migrated data for plan %r: %s", self.plan_id, e
                )
                ui.notify("Fehler beim Speichern der migrierten Daten", type="negative")
                return
            msg = f"Migration nach {version} erfolgreich"
            if not result.has_warnings:
                ui.notify(msg, type="positive")
            else:
                ui.notify(f"{msg}, Warnungen vorhanden", type="warning")
            # notify plan tree to update nodes
            for client in app.clients("/plan-tools/{id}"):
                client.run_javascript("emitEvent('featureAdded')")
            # notify plan manager to update table
            for client in app.clients("/plans"):
                client.run_javascript("emitEvent('planCreated')")
            logger.info("Sending migrated_plan data to QWebChannel handler")
            data = {"id": self.plan_id, "version": version}
            payload_json = json.dumps(data)
            ui.run_javascript(
                f"""
                    (window.ensureXmasHandler ? window.ensureXmasHandler() : Promise.reject('QWebChannel unavailable'))
                        .then(handler => handler.migrated_plan({payload_json}))
                        .catch(err => console.error('migrated_plan failed', err));
                """
            )
            self._show_report(reports)
        except Exception as e:
            logger.exception(
                "version migration for plan %r failed: %s", self.plan_id, e
            )
            ui.notify("Versionmigration fehlgeschlagen", type="negative")
        finally:
            self.spinner_box.clear()
            await self._get_select_options()
            self.start_button.enable()

    def _show_report(
        self, reports: dict[str, list[WarningIssue] | list[ErrorIssue]]
    ) -> None:
        def copy_issues_to_clipboard():
            ui.clipboard.write(
                "\n".join(
                    issue.message for report in reports.values() for issue in report
                )
            )

        with self.issue_list:
            ui.button(icon="copy_all", on_click=copy_issues_to_clipboard).classes(
                "float-right"
            ).set_visibility(
                False
            )  # disable for now until copying is enabled on Qt side
            for step, issues in reports.items():
                ui.item_label(f"Version {step}").props("header").classes("text-bold")
                if not issues:
                    with ui.item():
                        with ui.item_section().props("avatar"):
                            ui.icon("check_circle", color="positive")
                        with ui.item_section():
                            ui.item_label("keine Warnungen oder Fehler vorhanden")
                else:
                    for issue in issues:
                        with ui.item() as issue_item:
                            if issue.location:
                                issue_item.on_click(
                                    lambda loc=issue.location: self._show_action_menu(
                                        loc
                                    )
                                )
                            with ui.item_section().props("avatar"):
                                if isinstance(issue, WarningIssue):
                                    ui.icon("warning", color="warning")
                                else:
                                    ui.icon("error", color="negative")
                            with ui.item_section():
                                ui.item_label(issue.message)

    async def _show_action_menu(self, feature_id: str):
        feature = await run.io_bound(self.repo.get, feature_id)
        with self.action_dialog, ui.card():
            FeatureInteractionButton(
                "highlight_feature",
                feature_id,
                geom=feature.get_geom_wkt() is not None,
            )
            FeatureInteractionButton("select_feature", feature_id)
            FeatureInteractionButton("show_attribute_form", feature_id)
        self.action_dialog.open()
