"""
pywboard - Simple Annotation Whiteboard
PyQt6 + QWebEngine desktop wrapper for the Annotate tool

Usage:
    pywboard                    # Launch empty canvas
    pywboard topology.png       # Launch with image pre-loaded
    python -m pywboard          # Module execution
"""

import sys
import os
import json
import base64
import mimetypes
from pathlib import Path

from PyQt6.QtCore import (
    Qt, QUrl, QStandardPaths, pyqtSlot, QObject, QByteArray
)
from PyQt6.QtGui import QAction, QKeySequence, QIcon
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QFileDialog, QMessageBox
)
from PyQt6.QtWebEngineWidgets import QWebEngineView
from PyQt6.QtWebEngineCore import (
    QWebEngineProfile, QWebEnginePage, QWebEngineSettings
)
from PyQt6.QtWebChannel import QWebChannel

__version__ = "1.0.0"
APP_NAME = "pywboard"
WINDOW_TITLE = "pywboard — Simple Annotation Whiteboard"

# Resolve the HTML asset path
ASSET_DIR = Path(__file__).parent / "assets"
HTML_FILE = ASSET_DIR / "annotate.html"


class Bridge(QObject):
    """JS ↔ Python bridge via QWebChannel."""

    def __init__(self, window):
        super().__init__()
        self._window = window

    @pyqtSlot(result=str)
    def nativeOpenImage(self):
        """Open file dialog and return image as data URL, or empty string."""
        path, _ = QFileDialog.getOpenFileName(
            self._window,
            "Open Image",
            "",
            "Images (*.png *.jpg *.jpeg *.gif *.bmp *.svg *.webp);;All Files (*)"
        )
        if not path:
            return ""
        mime, _ = mimetypes.guess_type(path)
        if not mime:
            mime = "image/png"
        with open(path, "rb") as f:
            data = base64.b64encode(f.read()).decode("ascii")
        return f"data:{mime};base64,{data}"

    @pyqtSlot(str, str, result=bool)
    def nativeSaveExport(self, data_url, suggested_name):
        """Save a data URL to a user-chosen file path."""
        downloads = QStandardPaths.writableLocation(
            QStandardPaths.StandardLocation.DownloadLocation
        ) or str(Path.home())
        default_path = os.path.join(downloads, suggested_name)

        path, _ = QFileDialog.getSaveFileName(
            self._window,
            "Export Annotated Image",
            default_path,
            "PNG Image (*.png);;All Files (*)"
        )
        if not path:
            return False
        # Strip the data URL header
        header, encoded = data_url.split(",", 1)
        raw = base64.b64decode(encoded)
        with open(path, "wb") as f:
            f.write(raw)
        return True

    @pyqtSlot(result=str)
    def getVersion(self):
        return __version__


class WebPage(QWebEnginePage):
    """Custom page to suppress console noise and handle JS dialogs."""

    def javaScriptConsoleMessage(self, level, message, line, source):
        # Suppress verbose console output; uncomment for debug
        # print(f"[JS:{line}] {message}")
        pass


class MainWindow(QMainWindow):

    def __init__(self, image_path=None):
        super().__init__()
        self.setWindowTitle(WINDOW_TITLE)
        self.resize(1400, 900)
        self._pending_image = image_path

        # Dark window styling
        self.setStyleSheet("""
            QMainWindow { background: #1a1a1a; }
            QMenuBar { background: #252525; color: #e0e0e0; border-bottom: 1px solid #333; }
            QMenuBar::item:selected { background: #007AFF; }
            QMenu { background: #2a2a2a; color: #e0e0e0; border: 1px solid #444; }
            QMenu::item:selected { background: #007AFF; }
        """)

        self._setup_webview()
        self._setup_menus()
        self._setup_bridge()

    def _setup_webview(self):
        profile = QWebEngineProfile.defaultProfile()
        self._page = WebPage(profile, self)
        self._view = QWebEngineView(self)

        # Enable local file access and other needed settings
        settings = self._page.settings()
        settings.setAttribute(
            QWebEngineSettings.WebAttribute.LocalContentCanAccessFileUrls, True
        )
        settings.setAttribute(
            QWebEngineSettings.WebAttribute.LocalContentCanAccessRemoteUrls, False
        )
        settings.setAttribute(
            QWebEngineSettings.WebAttribute.JavascriptEnabled, True
        )
        settings.setAttribute(
            QWebEngineSettings.WebAttribute.JavascriptCanAccessClipboard, True
        )

        self._view.setPage(self._page)
        self.setCentralWidget(self._view)

        # Load the HTML
        url = QUrl.fromLocalFile(str(HTML_FILE.resolve()))
        self._page.loadFinished.connect(self._on_load_finished)
        self._view.setUrl(url)

    def _setup_bridge(self):
        self._bridge = Bridge(self)
        self._channel = QWebChannel(self._page)
        self._channel.registerObject("pywboard", self._bridge)
        self._page.setWebChannel(self._channel)

    def _setup_menus(self):
        menubar = self.menuBar()

        # File menu
        file_menu = menubar.addMenu("&File")

        open_act = QAction("&Open Image…", self)
        open_act.setShortcut(QKeySequence("Ctrl+O"))
        open_act.triggered.connect(self._native_open)
        file_menu.addAction(open_act)

        export_act = QAction("&Export PNG…", self)
        export_act.setShortcut(QKeySequence("Ctrl+Shift+S"))
        export_act.triggered.connect(self._native_export)
        file_menu.addAction(export_act)

        file_menu.addSeparator()

        quit_act = QAction("&Quit", self)
        quit_act.setShortcut(QKeySequence("Ctrl+Q"))
        quit_act.triggered.connect(self.close)
        file_menu.addAction(quit_act)

        # Edit menu
        edit_menu = menubar.addMenu("&Edit")

        undo_act = QAction("&Undo", self)
        undo_act.setShortcut(QKeySequence("Ctrl+Z"))
        undo_act.triggered.connect(
            lambda: self._run_js("document.getElementById('btn-undo').click()")
        )
        edit_menu.addAction(undo_act)

        clear_act = QAction("&Clear All", self)
        clear_act.triggered.connect(
            lambda: self._run_js("document.getElementById('btn-clear').click()")
        )
        edit_menu.addAction(clear_act)

        # View menu
        view_menu = menubar.addMenu("&View")

        fit_act = QAction("&Fit to Window", self)
        fit_act.setShortcut(QKeySequence("Ctrl+0"))
        fit_act.triggered.connect(
            lambda: self._run_js("document.querySelector('[data-mode=\"fit\"]').click()")
        )
        view_menu.addAction(fit_act)

        actual_act = QAction("&Actual Size (1:1)", self)
        actual_act.setShortcut(QKeySequence("Ctrl+1"))
        actual_act.triggered.connect(
            lambda: self._run_js("document.querySelector('[data-mode=\"scroll\"]').click()")
        )
        view_menu.addAction(actual_act)

        # Help menu
        help_menu = menubar.addMenu("&Help")
        about_act = QAction("&About pywboard", self)
        about_act.triggered.connect(self._show_about)
        help_menu.addAction(about_act)

    def _run_js(self, code, callback=None):
        if callback:
            self._page.runJavaScript(code, callback)
        else:
            self._page.runJavaScript(code)

    def _on_load_finished(self, ok):
        if not ok:
            return
        # Inject the QWebChannel bridge
        self._inject_bridge()
        # If launched with an image argument, load it
        if self._pending_image:
            self._load_image_file(self._pending_image)
            self._pending_image = None

    def _inject_bridge(self):
        """Inject JS that hooks the native bridge into the HTML UI."""
        # First load qwebchannel.js, then set up the bridge
        qrc_js = "qrc:///qtwebchannel/qwebchannel.js"
        loader = f"""
        (function() {{
            if (typeof QWebChannel !== 'undefined') {{
                setupPywboardBridge();
                return;
            }}
            var s = document.createElement('script');
            s.src = '{qrc_js}';
            s.onload = function() {{ setupPywboardBridge(); }};
            document.head.appendChild(s);
        }})();

        function setupPywboardBridge() {{
            new QWebChannel(qt.webChannelTransport, function(channel) {{
                window._pywboard = channel.objects.pywboard;

                // Override the LOAD IMAGE button to use native dialog
                var loadBtn = document.getElementById('btn-load');
                var newBtn = loadBtn.cloneNode(true);
                loadBtn.parentNode.replaceChild(newBtn, loadBtn);
                newBtn.addEventListener('click', function() {{
                    window._pywboard.nativeOpenImage(function(dataUrl) {{
                        if (!dataUrl) return;
                        var img = new Image();
                        img.onload = function() {{
                            window.dispatchEvent(new CustomEvent('pywboard-load-image', {{
                                detail: {{ image: img }}
                            }}));
                        }};
                        img.src = dataUrl;
                    }});
                }});

                // Override EXPORT PNG to use native save dialog
                var exportBtn = document.getElementById('btn-export');
                var newExport = exportBtn.cloneNode(true);
                exportBtn.parentNode.replaceChild(newExport, exportBtn);
                newExport.addEventListener('click', function() {{
                    var canvas = document.getElementById('draw-canvas');
                    var dataUrl = canvas.toDataURL('image/png');
                    window._pywboard.nativeSaveExport(dataUrl, 'annotated-diagram.png', function(ok) {{}});
                }});

                console.log('pywboard bridge ready');
            }});
        }}
        """
        self._run_js(loader)

    def _native_open(self):
        """Menu File > Open — trigger the bridge open."""
        js = """
        if (window._pywboard) {
            window._pywboard.nativeOpenImage(function(dataUrl) {
                if (!dataUrl) return;
                var img = new Image();
                img.onload = function() {
                    window.dispatchEvent(new CustomEvent('pywboard-load-image', {
                        detail: { image: img }
                    }));
                };
                img.src = dataUrl;
            });
        } else {
            document.getElementById('file-input').click();
        }
        """
        self._run_js(js)

    def _native_export(self):
        """Menu File > Export."""
        js = """
        if (window._pywboard) {
            var canvas = document.getElementById('draw-canvas');
            var dataUrl = canvas.toDataURL('image/png');
            window._pywboard.nativeSaveExport(dataUrl, 'annotated-diagram.png', function(ok) {});
        }
        """
        self._run_js(js)

    def _load_image_file(self, path):
        """Load an image file into the canvas by injecting it as a data URL."""
        path = os.path.abspath(path)
        if not os.path.isfile(path):
            return
        mime, _ = mimetypes.guess_type(path)
        if not mime:
            mime = "image/png"
        with open(path, "rb") as f:
            data = base64.b64encode(f.read()).decode("ascii")
        data_url = f"data:{mime};base64,{data}"
        js = f"""
        (function() {{
            var img = new Image();
            img.onload = function() {{
                window.dispatchEvent(new CustomEvent('pywboard-load-image', {{
                    detail: {{ image: img }}
                }}));
            }};
            img.src = "{data_url}";
        }})();
        """
        self._run_js(js)

    def _show_about(self):
        QMessageBox.about(
            self,
            "About pywboard",
            f"<h3>pywboard v{__version__}</h3>"
            "<p>Simple Annotation Whiteboard</p>"
            "<p>Pen, line, arrow, and text annotation tool "
            "for network topology diagrams.</p>"
            "<p style='color:#888;font-size:small;'>"
            "Built with PyQt6 + QWebEngine</p>"
        )

    def dragEnterEvent(self, event):
        if event.mimeData().hasUrls():
            event.acceptProposedAction()

    def dropEvent(self, event):
        urls = event.mimeData().urls()
        if urls:
            path = urls[0].toLocalFile()
            if path:
                self._load_image_file(path)


def main():
    # High-DPI support
    app = QApplication(sys.argv)
    app.setApplicationName(APP_NAME)
    app.setApplicationVersion(__version__)
    app.setOrganizationName("pywboard")

    # Dark palette for native widgets
    from PyQt6.QtGui import QPalette, QColor
    palette = QPalette()
    palette.setColor(QPalette.ColorRole.Window, QColor("#1a1a1a"))
    palette.setColor(QPalette.ColorRole.WindowText, QColor("#e0e0e0"))
    palette.setColor(QPalette.ColorRole.Base, QColor("#252525"))
    palette.setColor(QPalette.ColorRole.Text, QColor("#e0e0e0"))
    palette.setColor(QPalette.ColorRole.Button, QColor("#333333"))
    palette.setColor(QPalette.ColorRole.ButtonText, QColor("#e0e0e0"))
    palette.setColor(QPalette.ColorRole.Highlight, QColor("#007AFF"))
    palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
    app.setPalette(palette)

    # Check for image argument
    image_path = None
    args = sys.argv[1:]
    if args and os.path.isfile(args[0]):
        image_path = args[0]

    window = MainWindow(image_path=image_path)
    window.show()

    sys.exit(app.exec())


if __name__ == "__main__":
    main()
