# v6

from __future__ import annotations
import logging
import os
import sys
from logging.handlers import RotatingFileHandler
import traceback

from theme import (
    apply_dark_palette,
    apply_stylesheet,
)
from PySide6.QtCore import Qt
from PySide6.QtGui import QIcon
from PySide6.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QMessageBox,
    QStatusBar,

)
# whats needed
from browser import BrowserView
from config import (
    APP_NAME,
    APP_VERSION,
    DATABASE_PATH,
    LOG_LEVEL,
    LOGS_DIR,
    START_URL,
    WINDOW_HEIGHT,
    WINDOW_WIDTH,
    ensure_directories,
    load_settings,
    save_settings,
)
from db import Database

from managers import (
    AddonManagerWindow,
    NetworkInspector,
    PluginManagerWindow,
    ProfileManager,
    SettingsWindow,
    TrayManager,
    ContextManager,
)

# v2
os.environ.setdefault(
    "QTWEBENGINE_CHROMIUM_FLAGS",
    (
        "--disable-features="
        "Translate,"
        "MediaRouter "
        "--enable-features="
        "DnsOverHttps "
        "--dns-over-https-mode=secure "
        "--dns-over-https-templates="
        "https://dns.quad9.net/dns-query "
        "--disable-background-networking "
        "--disable-component-update "
        "--disable-domain-reliability "
        "--disable-breakpad "
        "--no-pings "
        "--enable-gpu-rasterization "
        "--enable-zero-copy "
        "--disable-sync "
        "--disable-translate "
        "--disable-features=AutofillServerCommunication "
        "--enable-native-gpu-memory-buffers "
    ),
)


LOG_FILE = LOGS_DIR / "client.log"


def configure_logging() -> None:
    LOG_FILE.parent.mkdir(
        parents=True,
        exist_ok=True,
    )

    root = logging.getLogger()

    root.setLevel(
        LOG_LEVEL
    )

    root.handlers.clear()

    formatter = logging.Formatter(
        "%(asctime)s %(levelname)s %(name)s: %(message)s"
    )

    file_handler = (
        RotatingFileHandler(
            LOG_FILE,
            maxBytes=10 * 1024 * 1024,
            backupCount=5,
            encoding="utf-8",
        )
    )

    file_handler.setFormatter(
        formatter
    )

    console_handler = (
        logging.StreamHandler()
    )

    console_handler.setFormatter(
        formatter
    )

    root.addHandler(
        file_handler
    )

    root.addHandler(
        console_handler
    )


configure_logging()

log = logging.getLogger(
    __name__
)


class MainWindow(QMainWindow):
    def __init__(
        self,
        db: Database,
        settings: dict,
    ):
        super().__init__()

        self.db = db
        self.settings_data = settings

        self.contexts = ContextManager(
            self.db,
            self.settings_data,
        )

        self.plugins_window = None
        self.settings_window = None
        self.profile_window = None
        self.addons_window = None
        self.network_window = None
        self.tray_manager = None

        self.setWindowTitle(
            f"{APP_NAME}---{APP_VERSION}"
        )

        self.resize(
            max(
                800,
                int(
                    settings.get(
                        "window_width",
                        WINDOW_WIDTH,
                    )
                ),
            ),
            max(
                600,
                int(
                    settings.get(
                        "window_height",
                        WINDOW_HEIGHT,
                    )
                ),
            ),
        )

        self.browser = BrowserView(
            db=self.db,
            context=self.context,
            settings=self.settings_data,
            parent=self,
        )

        self.setCentralWidget(
            self.browser
        )

        self._build_status_bar()
        self._connect_signals()
        self._build_tray()

        self.browser.open(
            START_URL
        )

    @property
    def context(self):
        return self.contexts.current()

    def _build_status_bar(
        self,
    ) -> None:
        self.status_bar = (
            QStatusBar(
                self
            )
        )

        self.setStatusBar(
            self.status_bar
        )

        self.download_label = QLabel(
            "Ready",
            self,
        )

        self.status_bar.addPermanentWidget(
            self.download_label
        )
        self.status_bar.addPermanentWidget(
            QLabel(
                f"Blocked: "
                f"{self.browser.network.blocklist_engine.count():,}"
            )
        )

    def _connect_signals(
        self,
    ) -> None:
        self.browser.downloadProgress.connect(
            self._download_progress
        )

        self.browser.page_.consoleMessage.connect(
            self._console_message
        )

        self.browser.bridge.messageReceived.connect(
            self._bridge_message
        )

    def _build_tray(
        self,
    ) -> None:
        tray_cfg = self.settings_data.get(
            "tray",
            {},
        )

        if not tray_cfg.get(
            "enabled",
            True,
        ):
            return

        self.tray_manager = (
            TrayManager(
                self
            )
        )

        self.tray_manager.open_action.triggered.connect(
            self._show_window
        )

        self.tray_manager.tray.show()

    def _show_window(
        self,
    ) -> None:
        self.showNormal()
        self.raise_()
        self.activateWindow()

    def _console_message(
        self,
        message: str,
    ) -> None:
        ignored = {
            "setMemoryInformation not available",
            "[ProcessUtilsElectron]",
        }

        if any(
            token in message
            for token in ignored
        ):
            return

        log.info(
            "console=%s",
            message,
        )

    def _bridge_message(
        self,
        message: str,
    ) -> None:
        log.info(
            "bridge=%s",
            message,
        )

    def _download_progress(
        self,
        filename: str,
        received: int,
        total: int,
    ) -> None:
        if total <= 0:
            self.download_label.setText(
                f"{filename}: {received:,} B"
            )
            return

        percent = int(
            (
                received
                / max(
                    total,
                    1,
                )
            )
            * 100
        )

        self.download_label.setText(
            (
                f"{filename}: "
                f"{percent}% "
                f"({received:,}/{total:,})"
            )
        )

    def open_settings(
        self,
    ) -> None:
        window = SettingsWindow(
            self.settings_data,
            self,
        )

        window.settingsChanged.connect(
            self._settings_changed
        )

        self.settings_window = window

        window.exec()

    def open_plugins(
        self,
    ) -> None:
        self.plugins_window = (
            PluginManagerWindow(
                self.browser,
                self.db,
                self.context.name,
                self,
            )
        )

        self.plugins_window.show()

    def open_profiles(
        self,
    ) -> None:
        window = ProfileManager(
            self.db.list_profiles(),
            self.settings_data.get(
                "active_profile",
                "default",
            ),
            self,
        )

        window.profileSelected.connect(
            self._profile_selected
        )

        self.profile_window = window

        window.exec()

    def open_addons(
        self,
    ) -> None:
        self.addons_window = (
            AddonManagerWindow(
                self.context,
                self,
            )
        )

        self.addons_window.show()

    def open_network(
        self,
    ) -> None:
        self.network_window = (
            NetworkInspector(
                self.browser.network,
                self,
            )
        )

        self.network_window.show()

    def open_devtools(
        self,
    ) -> None:
        self.browser.open_devtools()

    def _settings_changed(
        self,
    ) -> None:
        save_settings(
            self.settings_data
        )

    def _profile_selected(
        self,
        profile_name: str,
    ) -> None:
        current = (
            self.settings_data.get(
                "active_profile"
            )
        )

        if profile_name == current:
            return

        self.settings_data[
            "active_profile"
        ] = profile_name

        save_settings(
            self.settings_data
        )

        QMessageBox.information(
            self,
            APP_NAME,
            (
                "Profile updated.\n\n"
                "Restart required."
            ),
        )

    def _persist_window_state(
        self,
    ) -> None:
        size = self.size()

        self.settings_data[
            "window_width"
        ] = size.width()

        self.settings_data[
            "window_height"
        ] = size.height()

        save_settings(
            self.settings_data
        )

    def _shutdown(
        self,
    ) -> None:
        try:
            self.browser.shutdown()
        except Exception:
            log.exception(
                "browser shutdown failed"
            )

        try:
            self.db.shutdown()
        except Exception:
            log.exception(
                "database shutdown failed"
            )

    # v2
    def closeEvent(
        self,
        event,
    ):
        if getattr(
            self,
            "_closing",
            False,
        ):
            event.accept()
            return

        tray_cfg = self.settings_data.get(
            "tray",
            {},
        )

        if (
            tray_cfg.get(
                "close_to_tray",
                True,
            )
            and self.tray_manager
        ):
            event.ignore()
            self.hide()
            return

        self._closing = True

        try:
            self._persist_window_state()

            for widget in (
                self.plugins_window,
                self.settings_window,
                self.profile_window,
                self.addons_window,
                self.network_window,
            ):
                if widget:
                    try:
                        widget.close()
                    except Exception:
                        log.exception(
                            "child window close failed"
                        )

            self._shutdown()

        except Exception:
            log.exception(
                "shutdown failed"
            )

        event.accept()

        super().closeEvent(
            event
        )



def build_database() -> Database:
    return Database(
        DATABASE_PATH
    )

# v3
def build_app() -> QApplication:
    os.environ.setdefault(
        "QTWEBENGINE_REMOTE_DEBUGGING",
        "0",
    )

    os.environ.setdefault(
        "CHROME_CRASHPAD_PIPE_NAME",
        "",
    )

    QApplication.setAttribute(
        Qt.ApplicationAttribute.AA_ShareOpenGLContexts
    )

    return QApplication(
        sys.argv
    )


def fatal_error(
    text: str,
) -> None:
    try:
        app = QApplication.instance()

        if app is None:
            app = QApplication(
                sys.argv
            )

        QMessageBox.critical(
            None,
            APP_NAME,
            text,
        )

    except Exception:
        pass
# v3
def main() -> int:
    try:
        ensure_directories()

        settings = load_settings()

        db = build_database()

        QApplication.setDesktopSettingsAware(
            True
        )

        app = build_app()

        icon = QIcon(
            "app.ico"
        )

        if icon.isNull():

            for candidate in (
                "ui/app.ico",
                "assets/app.ico",
                "icon.ico",
            ):
                test = QIcon(
                    candidate
                )

                if not test.isNull():
                    icon = test
                    break

        if not icon.isNull():
            app.setWindowIcon(
                icon
            )

        app.setApplicationName(
            APP_NAME
        )

        app.setApplicationVersion(
            APP_VERSION
        )

        app.setStyle(
            "Fusion"
        )

        apply_dark_palette(
            app
        )

        app.setStyleSheet(
            apply_stylesheet()
        )

        window = MainWindow(
            db=db,
            settings=settings,
        )

        window.show()

        return app.exec()

    except KeyboardInterrupt:
        return 0

    except SystemExit:
        return 0

    except Exception as exc:
        log.exception(
            "fatal startup error"
        )

        fatal_error(
            str(exc)
        )

        return 1


if __name__ == "__main__":
    raise SystemExit(
        main()
    )