# -*- coding: utf-8 -*-
"""
LVT4U — Flycam Photos: Photo Viewer Window.

Interactive photo viewer with zoom, pan, rotate, navigation,
and optional image filters (basic + OpenCV).

Author: Lộc Vũ Trung (LVT) / Slow Forest
License: GPL-3.0

Based on ImportPhotos plugin (GPL-2.0+) by KIOS Research Center.
"""


import os

from qgis.PyQt.QtCore import Qt
from qgis.PyQt.QtGui import (
    QPixmap, QImage, QPainter, QTransform,
)
from qgis.PyQt.QtWidgets import (
    QDialog, QVBoxLayout, QLabel,
    QToolBar, QGraphicsView, QGraphicsScene,
    QFileDialog, QComboBox,
    QStatusBar,
)

from ..shared.i18n import tr

# Check OpenCV availability
try:
    import cv2
    import numpy as np
    HAS_OPENCV = True
except ImportError:
    HAS_OPENCV = False


class PhotoViewer(QGraphicsView):
    """Zoomable, pannable QGraphicsView for displaying photos."""

    def __init__(self, parent=None):
        super().__init__(parent)
        self._scene = QGraphicsScene(self)
        self.setScene(self._scene)
        self._pixmap_item = None
        self._zoom = 0

        self.setRenderHints(
            QPainter.Antialiasing | QPainter.SmoothPixmapTransform
        )
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def set_photo(self, pixmap):
        """Display a QPixmap in the viewer."""
        self._scene.clear()
        if pixmap and not pixmap.isNull():
            self._pixmap_item = self._scene.addPixmap(pixmap)
            self._zoom = 0
            self.fitInView(
                self._pixmap_item, Qt.KeepAspectRatio
            )
        else:
            self._pixmap_item = None

    def fit_view(self):
        """Fit the image to the view."""
        if self._pixmap_item:
            self._zoom = 0
            self.fitInView(
                self._pixmap_item, Qt.KeepAspectRatio
            )

    def wheelEvent(self, event):
        """Zoom in/out with mouse wheel."""
        if self._pixmap_item is None:
            return

        delta = event.angleDelta().y()
        if delta > 0:
            factor = 1.25
            self._zoom += 1
        else:
            factor = 0.8
            self._zoom -= 1

        if self._zoom > 0:
            self.scale(factor, factor)
        elif self._zoom == 0:
            self.fit_view()
        else:
            self._zoom = 0

    def get_current_pixmap(self):
        """Return the currently displayed pixmap."""
        if self._pixmap_item:
            return self._pixmap_item.pixmap()
        return None


class PhotoWindow(QDialog):
    """Photo viewer dialog with toolbar for navigation and filters."""

    def __init__(self, parent=None, iface=None):
        super().__init__(parent)
        self.iface = iface
        self.setWindowTitle(tr("Flycam Photo Viewer"))
        self.setMinimumSize(700, 500)
        self.resize(900, 650)

        self._photo_list = []
        self._current_index = -1
        self._current_path = ''
        self._original_pixmap = None
        self._rotation = 0
        self._current_filter = 'None'

        self._build_ui()

    def _build_ui(self):
        """Build the viewer UI."""
        layout = QVBoxLayout(self)
        layout.setContentsMargins(2, 2, 2, 2)
        layout.setSpacing(2)

        # --- Toolbar ---
        toolbar = QToolBar()
        toolbar.setIconSize(toolbar.iconSize())

        # Navigation
        self._act_prev = toolbar.addAction("◀")
        self._act_prev.setToolTip(tr("Previous photo"))
        self._act_prev.triggered.connect(self._prev_photo)

        self._act_next = toolbar.addAction("▶")
        self._act_next.setToolTip(tr("Next photo"))
        self._act_next.triggered.connect(self._next_photo)

        toolbar.addSeparator()

        # View controls
        self._act_fit = toolbar.addAction("⊞")
        self._act_fit.setToolTip(tr("Fit to window"))
        self._act_fit.triggered.connect(self._fit)

        self._act_rotate_cw = toolbar.addAction("↻")
        self._act_rotate_cw.setToolTip(tr("Rotate 90° clockwise"))
        self._act_rotate_cw.triggered.connect(self._rotate_cw)

        self._act_rotate_az = toolbar.addAction("🧭")
        self._act_rotate_az.setToolTip(tr("Rotate to azimuth (north-up)"))
        self._act_rotate_az.triggered.connect(self._rotate_azimuth)

        toolbar.addSeparator()

        # Filters
        self._filter_combo = QComboBox()
        self._filter_combo.setMinimumWidth(120)
        filters = ["None", "Gray", "Mirror", "Mono"]
        if HAS_OPENCV:
            filters.extend([
                "Edges", "Red Band", "Green Band", "Blue Band",
                "Gaussian Blur"
            ])
        self._filter_combo.addItems(filters)
        self._filter_combo.currentTextChanged.connect(self._apply_filter)
        toolbar.addWidget(QLabel(f"  {tr('Filter')}: "))
        toolbar.addWidget(self._filter_combo)

        toolbar.addSeparator()

        # Save
        self._act_save = toolbar.addAction("💾")
        self._act_save.setToolTip(tr("Save current view as image"))
        self._act_save.triggered.connect(self._save_as)

        layout.addWidget(toolbar)

        # --- Photo Viewer ---
        self._viewer = PhotoViewer()
        layout.addWidget(self._viewer, 1)

        # --- Status Bar ---
        self._status = QStatusBar()
        self._status.setSizeGripEnabled(False)
        layout.addWidget(self._status)

    # ----- Public API -----

    def show_photo(self, photo_path, attrs=None, photo_list=None):
        """Display a photo with optional metadata.

        Args:
            photo_path: Absolute path to the image.
            attrs: dict of feature attributes (Date, Time, Altitude, etc.)
            photo_list: list of all photo paths for navigation.
        """
        if photo_list:
            self._photo_list = photo_list
            try:
                self._current_index = photo_list.index(photo_path)
            except ValueError:
                self._current_index = 0

        self._current_path = photo_path
        self._rotation = 0
        self._filter_combo.setCurrentText("None")
        self._current_filter = 'None'

        # Load pixmap
        self._original_pixmap = QPixmap(photo_path)
        if self._original_pixmap.isNull():
            self._status.showMessage(f"❌ Cannot load: {photo_path}")
            return

        self._viewer.set_photo(self._original_pixmap)

        # Update status bar
        name = os.path.basename(photo_path)
        info_parts = [name]
        if attrs:
            if attrs.get('Date'):
                info_parts.append(f"📅 {attrs['Date']}")
            if attrs.get('Time'):
                info_parts.append(f"⏰ {attrs['Time']}")
            if attrs.get('Altitude'):
                info_parts.append(f"🏔 {attrs['Altitude']:.1f}m")
            if attrs.get('Azimuth'):
                info_parts.append(f"🧭 {attrs['Azimuth']:.1f}°")
            if attrs.get('Cam. Model') or attrs.get('Cam_Model'):
                model = attrs.get('Cam. Model') or attrs.get('Cam_Model', '')
                if model:
                    info_parts.append(f"📷 {model}")

        idx_info = ""
        if self._photo_list:
            idx_info = f" [{self._current_index + 1}/{len(self._photo_list)}]"

        self._status.showMessage("  |  ".join(info_parts) + idx_info)

        # Update window title
        self.setWindowTitle(
            f"{tr('Flycam Photo Viewer')} — {name}"
        )

        self.show()
        self.raise_()
        self.activateWindow()

    # ----- Navigation -----

    def _prev_photo(self):
        """Show previous photo in list."""
        if not self._photo_list or self._current_index <= 0:
            return
        self._current_index -= 1
        path = self._photo_list[self._current_index]
        self.show_photo(path)

    def _next_photo(self):
        """Show next photo in list."""
        if not self._photo_list or self._current_index >= len(self._photo_list) - 1:
            return
        self._current_index += 1
        path = self._photo_list[self._current_index]
        self.show_photo(path)

    # ----- View controls -----

    def _fit(self):
        """Fit image to window."""
        self._viewer.fit_view()

    def _rotate_cw(self):
        """Rotate 90° clockwise."""
        self._rotation = (self._rotation + 90) % 360
        self._refresh_display()

    def _rotate_azimuth(self):
        """Rotate image based on azimuth value (north-up orientation)."""
        if not self._current_path:
            return

        # Try to get azimuth from EXIF
        from .exif_reader import read_photo_exif
        info = read_photo_exif(self._current_path)
        if info and info.get('azimuth', 0) != 0:
            self._rotation = -info['azimuth']
            self._refresh_display()

    def _refresh_display(self):
        """Re-render the photo with current rotation and filter."""
        if self._original_pixmap is None:
            return

        pixmap = self._apply_filter_to_pixmap(
            self._original_pixmap, self._current_filter
        )

        if self._rotation != 0:
            transform = QTransform().rotate(self._rotation)
            pixmap = pixmap.transformed(transform, Qt.SmoothTransformation)

        self._viewer.set_photo(pixmap)

    # ----- Filters -----

    def _apply_filter(self, filter_name):
        """Apply a filter when combo box changes."""
        self._current_filter = filter_name
        self._refresh_display()

    def _apply_filter_to_pixmap(self, pixmap, filter_name):
        """Apply an image filter to a QPixmap.

        Args:
            pixmap: Source QPixmap.
            filter_name: Name of the filter.

        Returns:
            QPixmap: Filtered pixmap.
        """
        if filter_name == 'None' or not pixmap:
            return pixmap

        # Basic filters (no OpenCV)
        if filter_name == 'Gray':
            image = pixmap.toImage()
            gray = image.convertToFormat(QImage.Format_Grayscale8)
            return QPixmap.fromImage(gray)

        if filter_name == 'Mirror':
            return pixmap.transformed(QTransform().scale(-1, 1))

        if filter_name == 'Mono':
            image = pixmap.toImage()
            mono = image.convertToFormat(QImage.Format_Mono)
            return QPixmap.fromImage(mono)

        # OpenCV filters
        if not HAS_OPENCV:
            return pixmap

        # Convert QPixmap to numpy array
        image = pixmap.toImage()
        image = image.convertToFormat(QImage.Format_RGBA8888)
        width = image.width()
        height = image.height()

        ptr = image.bits()
        ptr.setsize(height * width * 4)
        arr = np.array(ptr, dtype=np.uint8).reshape((height, width, 4))
        bgr = cv2.cvtColor(arr, cv2.COLOR_RGBA2BGR)

        if filter_name == 'Edges':
            gray = cv2.cvtColor(bgr, cv2.COLOR_BGR2GRAY)
            edges = cv2.Canny(gray, 100, 200)
            result = cv2.cvtColor(edges, cv2.COLOR_GRAY2BGR)
        elif filter_name == 'Red Band':
            result = bgr.copy()
            result[:, :, 0] = 0  # Blue = 0
            result[:, :, 1] = 0  # Green = 0
        elif filter_name == 'Green Band':
            result = bgr.copy()
            result[:, :, 0] = 0  # Blue = 0
            result[:, :, 2] = 0  # Red = 0
        elif filter_name == 'Blue Band':
            result = bgr.copy()
            result[:, :, 1] = 0  # Green = 0
            result[:, :, 2] = 0  # Red = 0
        elif filter_name == 'Gaussian Blur':
            result = cv2.GaussianBlur(bgr, (15, 15), 0)
        else:
            return pixmap

        # Convert back to QPixmap
        rgb = cv2.cvtColor(result, cv2.COLOR_BGR2RGB)
        h, w, ch = rgb.shape
        bytes_per_line = ch * w
        q_image = QImage(rgb.data, w, h, bytes_per_line, QImage.Format_RGB888)
        return QPixmap.fromImage(q_image.copy())

    # ----- Save -----

    def _save_as(self):
        """Save the current view as an image file."""
        current_pm = self._viewer.get_current_pixmap()
        if current_pm is None:
            return

        path, _ = QFileDialog.getSaveFileName(
            self, tr("Save Image As"),
            os.path.splitext(self._current_path)[0] + "_edited.jpg",
            "JPEG (*.jpg);;PNG (*.png);;BMP (*.bmp)"
        )
        if path:
            current_pm.save(path)
            if self.iface:
                self.iface.messageBar().pushSuccess(
                    "LVT4U — Flycam",
                    f"✅ Saved: {path}"
                )

    # ----- Keyboard shortcuts -----

    def keyPressEvent(self, event):
        """Handle keyboard shortcuts."""
        key = event.key()
        if key == Qt.Key_Left:
            self._prev_photo()
        elif key == Qt.Key_Right:
            self._next_photo()
        elif key == Qt.Key_F:
            self._fit()
        elif key == Qt.Key_R:
            self._rotate_cw()
        elif key == Qt.Key_F11:
            if self.isFullScreen():
                self.showNormal()
            else:
                self.showFullScreen()
        else:
            super().keyPressEvent(event)
