from __future__ import annotations

"""Interpretation tool for Profile View.

This module provides the ProfileInterpretationTool for drawing
interpretation polygons in the profile preview window.
"""

import contextlib
import datetime
import random
import uuid

from qgis.core import (
    QgsMapLayer,
    QgsPointLocator,
    QgsPointXY,
    QgsProject,
    QgsVectorLayer,
    QgsWkbTypes,
)
from qgis.gui import (
    QgsMapCanvas,
    QgsMapToolEmitPoint,
    QgsRubberBand,
    QgsVertexMarker,
)
from qgis.PyQt.QtCore import QCoreApplication, QPoint, Qt, pyqtSignal
from qgis.PyQt.QtGui import QColor

from sec_interp.core.domain import InterpretationPolygon
from sec_interp.logger_config import get_logger, log_critical_operation

logger = get_logger(__name__)


class ProfileSnapper:
    """Helper class to handle point snapping functionality.

    Duplicates logic from ProfileMeasureTool to avoid tight coupling.
    """

    def __init__(self, canvas: QgsMapCanvas):
        self.canvas = canvas
        self._locators: dict[str, QgsPointLocator] = {}

    def snap(self, mouse_pos: QPoint) -> QgsPointXY:
        """Find the nearest vertex or edge to the mouse position."""
        point = self.canvas.getCoordinateTransform().toMapCoordinates(mouse_pos)

        # Search tolerance in map units (approx 12 pixels)
        tolerance = (self.canvas.mapUnitsPerPixel() or 1.0) * 12

        best_match = None
        best_dist = float("inf")

        layers = self.canvas.layers()
        current_layer_ids = {layer.id() for layer in layers if layer is not None}
        self._cleanup_locators(current_layer_ids)

        crs = self.canvas.mapSettings().destinationCrs()
        context = QgsProject.instance().transformContext()

        for layer in layers:
            if not self._is_snappable(layer):
                continue

            try:
                locator = self._get_locator(layer, crs, context)
                if not locator:
                    continue

                # Try vertex snap
                v_match = locator.nearestVertex(point, tolerance)
                if v_match.isValid() and v_match.distance() < best_dist:
                    best_match = v_match
                    best_dist = v_match.distance()

                # Try edge snap
                e_match = locator.nearestEdge(point, tolerance)
                if e_match.isValid() and e_match.distance() < best_dist:
                    best_match = e_match
                    best_dist = e_match.distance()
            except Exception:
                # If layer was deleted or something went wrong with locator
                continue

        if best_match:
            return best_match.point()

        return point

    def _cleanup_locators(self, current_ids: set[str]):
        """Remove locators for layers that are no longer active."""
        hits_to_remove = [lid for lid in self._locators if lid not in current_ids]
        for lid in hits_to_remove:
            del self._locators[lid]

    def _is_snappable(self, layer: QgsMapLayer) -> bool:
        """Check if a layer is valid for snapping."""
        return bool(layer and layer.type() == QgsMapLayer.VectorLayer)

    def _get_locator(self, layer: QgsVectorLayer, crs, context) -> QgsPointLocator | None:
        """Retrieve or create a locator for a layer."""
        if layer.id() not in self._locators:
            try:
                self._locators[layer.id()] = QgsPointLocator(layer, crs, context)
            except Exception as e:
                logger.warning(f"Failed to create locator for layer {layer.name()}: {e}")
                return None
        return self._locators[layer.id()]


class ProfileInterpretationTool(QgsMapToolEmitPoint):
    """Tool for drawing interpretation polygons on the profile canvas.

    - Left Click: Add vertex
    - Move: Update preview rubber band
    - Right Click: Remove last vertex
    - Enter/Double Click: Finalize polygon
    - Escape: Cancel
    """

    polygonFinished = pyqtSignal(InterpretationPolygon)

    def __init__(self, canvas: QgsMapCanvas):
        super().__init__(canvas)
        self.canvas = canvas
        self.points: list[QgsPointXY] = []
        self.rubber_band: QgsRubberBand | None = None
        self.vertex_markers: list[QgsVertexMarker] = []
        self.snapper = ProfileSnapper(canvas)
        self.cursor = Qt.CrossCursor

    def activate(self) -> None:
        """Activate the interpretation tool."""
        log_critical_operation(logger, "activate_interpretation_tool")
        logger.debug("ProfileInterpretationTool.activate() called")
        super().activate()
        self.canvas.setCursor(self.cursor)
        logger.debug("ProfileInterpretationTool activated successfully")

    def deactivate(self) -> None:
        """Deactivate the interpretation tool."""
        log_critical_operation(logger, "deactivate_interpretation_tool")
        logger.debug("ProfileInterpretationTool.deactivate() called")
        self.reset()
        super().deactivate()
        logger.debug("ProfileInterpretationTool deactivated successfully")

    def reset(self):
        """Reset the tool state safely."""
        log_critical_operation(
            logger,
            "reset_interpretation_tool",
            points=len(self.points) if self.points else 0,
        )
        logger.debug(
            f"ProfileInterpretationTool.reset() called - {len(self.points)} points, {len(self.vertex_markers)} markers"
        )
        self.points = []

        # Rubber band cleanup
        if self.rubber_band:
            with contextlib.suppress(Exception):
                self.rubber_band.reset(QgsWkbTypes.PolygonGeometry)
                self.canvas.scene().removeItem(self.rubber_band)
            self.rubber_band = None

        # Markers cleanup
        for marker in self.vertex_markers:
            with contextlib.suppress(Exception):
                self.canvas.scene().removeItem(marker)
        self.vertex_markers = []

        self.is_drawing = False
        if self.canvas:
            logger.debug("ProfileInterpretationTool.reset() - refreshing canvas")
            self.canvas.refresh()
        logger.debug("ProfileInterpretationTool.reset() completed")

    def canvasReleaseEvent(self, event: Any) -> None:
        """Handle mouse click release.

        Args:
            event: Map tool event from QGIS

        """
        if event.button() == Qt.RightButton:
            if self.points:
                self._remove_last_point()
            return

        snapped_point = self.snapper.snap(event.pos())
        self._add_point(snapped_point)

    def canvasMoveEvent(self, event: Any) -> None:
        """Handle mouse move for rubber band update.

        Args:
            event: Map tool event from QGIS

        """
        if not self.points:
            return
        current_point = self.snapper.snap(event.pos())
        self._update_rubber_band(current_point)

    def canvasDoubleClickEvent(self, event: Any) -> None:
        """Finalize polygon on double click.

        Args:
            event: Map tool event from QGIS

        """
        if len(self.points) >= 3:
            self.finalize_polygon()

    def keyPressEvent(self, event: Any) -> None:
        """Handle keyboard events.

        Args:
            event: Key event from QGIS

        """
        if event.key() in (Qt.Key_Return, Qt.Key_Enter):
            if len(self.points) >= 3:
                self.finalize_polygon()
                event.accept()
            return
        if event.key() == Qt.Key_Escape:
            self.reset()
            event.accept()
            return
        super().keyPressEvent(event)

    def _add_point(self, point: QgsPointXY):
        """Add a vertex to the current polygon."""
        # Prevent adding the exact same point twice in a row (e.g. slow click)
        if self.points and self.points[-1].compare(point, 1e-6):
            return

        self.points.append(point)
        self._ensure_rubber_band()
        self.rubber_band.addPoint(point, True)
        self._add_vertex_marker(point)

    def _remove_last_point(self):
        """Remove the last added vertex."""
        if not self.points:
            return
        self.points.pop()
        if self.vertex_markers:
            marker = self.vertex_markers.pop()
            self.canvas.scene().removeItem(marker)

        if not self.points:
            if self.rubber_band:
                self.canvas.scene().removeItem(self.rubber_band)
                self.rubber_band = None
        else:
            self.rubber_band.reset(QgsWkbTypes.PolygonGeometry)
            for p in self.points:
                self.rubber_band.addPoint(p, False)

    def _add_vertex_marker(self, point: QgsPointXY):
        """Add a visual marker for a vertex."""
        marker = QgsVertexMarker(self.canvas)
        marker.setCenter(point)
        marker.setColor(QColor(255, 165, 0))  # Orange
        marker.setIconSize(10)
        marker.setIconType(QgsVertexMarker.ICON_X)
        marker.setPenWidth(2)
        self.vertex_markers.append(marker)

    def _ensure_rubber_band(self):
        """Ensure the rubber band exists."""
        if self.rubber_band:
            return
        self.rubber_band = QgsRubberBand(self.canvas, QgsWkbTypes.PolygonGeometry)
        color = QColor(255, 0, 0, 100)  # Semi-transparent red
        self.rubber_band.setColor(color)
        self.rubber_band.setFillColor(color)
        self.rubber_band.setWidth(2)

    def _update_rubber_band(self, current_point: QgsPointXY):
        """Update rubber band geometry."""
        if not self.rubber_band or not self.points:
            return
        self.rubber_band.reset(QgsWkbTypes.PolygonGeometry)
        for p in self.points:
            self.rubber_band.addPoint(p, False)
        self.rubber_band.addPoint(current_point, True)

    def finalize_polygon(self):
        """Finalize the polygon and emit signal."""
        log_critical_operation(logger, "finalize_polygon", points=len(self.points))
        logger.debug(
            f"ProfileInterpretationTool.finalize_polygon() called with {len(self.points)} points"
        )
        if len(self.points) < 3:
            logger.warning("finalize_polygon() aborted - less than 3 points")
            return

        # Capture the points as (dist, elev) which are (x, y) in profile units
        vertices_2d = [(p.x(), p.y()) for p in self.points]

        # Generate a random vivid color for the new interpretation
        # Random hue (0-359), high saturation (200-255), medium-lightness (100-200)
        hue = random.randint(0, 359)
        sat = random.randint(200, 255)
        val = random.randint(150, 255)
        # Use simple hex format if QColor is not easily serializable, but QColor.name() works
        rand_color = QColor.fromHsv(hue, sat, val)
        color_hex = rand_color.name()  # e.g. #RRGGBB

        interp = InterpretationPolygon(
            id=str(uuid.uuid4()),
            name=QCoreApplication.translate("ProfileInterpretationTool", "New Interpretation"),
            type="lithology",
            vertices_2d=vertices_2d,
            attributes={},
            color=color_hex,
            created_at=datetime.datetime.now().isoformat(),
        )

        logger.debug(f"finalize_polygon() - emitting polygonFinished signal for {interp.id}")
        self.polygonFinished.emit(interp)
        # Note: Do NOT call reset() here.
        # The dialog handler should deactivate the tool, which calls reset() cleanly.
        logger.info(
            f"Interpretation polygon finalized with {len(vertices_2d)} vertices, ID: {interp.id}"
        )
        logger.debug("finalize_polygon() completed")
