#!/usr/bin/env python3
"""
BigLinux ISO Integrity Verification Tool (GTK4/Adwaita)

Checks MD5 checksums of Live CD squashfs files to detect
download corruption or USB drive errors before installation.
Fully accessible to ORCA screen reader via AT-SPI2.
"""

import gettext
import logging
import os
import signal
import sys
import threading
from pathlib import Path

import gi

gi.require_version("Gtk", "4.0")
gi.require_version("Adw", "1")

from gi.repository import Adw, Gdk, Gio, GLib, Gtk  # noqa: E402

installed_library = Path("/usr/lib/biglinux-livecd")
development_library = Path(__file__).resolve().parent.parent / "lib/biglinux-livecd"
library_directory = (
    installed_library
    if (installed_library / "integrity.py").is_file()
    else development_library
)
sys.path.insert(0, str(library_directory))

from integrity import (  # noqa: E402
    VerificationStatus,
    acquire_lock,
    clear_state,
    state_is_verified,
    verify_iso,
    write_state,
)

# ── i18n ──────────────────────────────────────────────────────────────────────
gettext.bindtextdomain("biglinux-livecd", "/usr/share/locale")
gettext.textdomain("biglinux-livecd")
_ = gettext.gettext

# ── Accessibility helper ──────────────────────────────────────────────────────
_HAS_ANNOUNCE = hasattr(Gtk.Accessible, "announce")


def announce(widget: Gtk.Accessible, message: str, assertive: bool = False) -> None:
    """Announce a message to screen readers via AT-SPI2."""
    if not message or not widget:
        return
    if _HAS_ANNOUNCE:
        try:
            priority = (
                Gtk.AccessibleAnnouncementPriority.HIGH
                if assertive
                else Gtk.AccessibleAnnouncementPriority.MEDIUM
            )
            widget.announce(message, priority)
        except Exception:
            pass


def set_label(widget: Gtk.Accessible, label: str) -> None:
    """Set the accessible label for a widget."""
    if widget and label:
        try:
            widget.update_property([Gtk.AccessibleProperty.LABEL], [label])
        except Exception:
            pass


def load_custom_css() -> None:
    """Load custom CSS matching the calamares pre-installer style."""
    css_provider = Gtk.CssProvider()
    css_data = b"""
    window.background {
        background-color: alpha(@theme_bg_color, 0.97);
    }
    .verify-progress {
        min-height: 8px;
    }
    """
    css_provider.load_from_data(css_data)
    display = Gdk.Display.get_default()
    if display:
        Gtk.StyleContext.add_provider_for_display(
            display, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
        )


# ── Application ───────────────────────────────────────────────────────────────
class VerifyApp(Adw.Application):
    """GTK4/Adw application for ISO integrity verification."""

    def __init__(self):
        super().__init__(
            application_id="com.biglinux.verify-md5sum",
            flags=Gio.ApplicationFlags.NON_UNIQUE,
        )
        self.connect("activate", self._on_activate)
        self._cancelled = False
        self._has_failure = False

    def _on_activate(self, _app):
        load_custom_css()
        self._build_ui()
        self._start_verification()

    def _build_ui(self):
        self._title = _("Checking system integrity")

        self.win = Adw.ApplicationWindow(
            application=self,
            title=self._title,
            default_width=700,
            default_height=500,
        )
        self.win.set_size_request(500, 380)
        self.win.set_deletable(True)
        self.win.connect("close-request", self._on_close_request)

        # ToolbarView + HeaderBar (same pattern as calamares window)
        toolbar_view = Adw.ToolbarView()
        self.win.set_content(toolbar_view)

        header_bar = Adw.HeaderBar()
        toolbar_view.add_top_bar(header_bar)

        # Stack for animated transitions between progress and result
        self.stack = Gtk.Stack()
        self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
        self.stack.set_transition_duration(300)
        toolbar_view.set_content(self.stack)

        # ── Progress page ─────────────────────────────────────────────
        self._build_progress_page()

        # ── Result pages (added dynamically) ──────────────────────────

        set_label(self.win, self._title)
        announce(self.win, self._title, assertive=True)
        self.win.present()

    def _build_progress_page(self):
        """Build the progress/checking page using Adw.StatusPage + ProgressBar."""
        progress_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)

        self.status_page = Adw.StatusPage()
        self.status_page.set_icon_name("drive-optical-symbolic")
        self.status_page.set_title(self._title)
        self.status_page.set_description(
            _(
                "Checking for download or USB drive errors, this may take a few minutes..."
            )
        )
        set_label(self.status_page, self._title)
        progress_box.append(self.status_page)

        # ProgressBar inside Adw.Clamp for consistent width
        clamp = Adw.Clamp()
        clamp.set_maximum_size(460)
        clamp.set_margin_start(24)
        clamp.set_margin_end(24)
        clamp.set_margin_bottom(32)

        progress_inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)

        self.file_label = Gtk.Label(label="", halign=Gtk.Align.CENTER)
        self.file_label.add_css_class("dim-label")
        self.file_label.add_css_class("caption")
        progress_inner.append(self.file_label)

        self.progress = Gtk.ProgressBar(show_text=True, hexpand=True)
        self.progress.add_css_class("verify-progress")
        set_label(self.progress, _("Verification progress"))
        progress_inner.append(self.progress)

        clamp.set_child(progress_inner)
        progress_box.append(clamp)

        self.stack.add_named(progress_box, "progress")
        self.stack.set_visible_child_name("progress")

    def _build_result_page(
        self, icon_name: str, title: str, description: str, is_error: bool
    ) -> str:
        """Build a result page using Adw.StatusPage and return its stack name."""
        page_name = "result-error" if is_error else "result-ok"

        result_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)

        status_page = Adw.StatusPage()
        status_page.set_icon_name(icon_name)
        status_page.set_title(title)
        status_page.set_description(description)
        set_label(status_page, f"{title}. {description}")
        result_box.append(status_page)

        # Close button inside Adw.Clamp
        btn_clamp = Adw.Clamp()
        btn_clamp.set_maximum_size(200)
        btn_clamp.set_margin_bottom(24)

        btn = Gtk.Button(label=_("Close"), hexpand=True)
        btn.add_css_class("pill")
        if is_error:
            btn.add_css_class("destructive-action")
        else:
            btn.add_css_class("suggested-action")
        set_label(btn, _("Close"))
        btn.connect("clicked", lambda _b: self.quit())
        btn_clamp.set_child(btn)
        result_box.append(btn_clamp)

        # Remove previous result page if exists (re-entrant safe)
        existing = self.stack.get_child_by_name(page_name)
        if existing:
            self.stack.remove(existing)

        self.stack.add_named(result_box, page_name)
        return page_name

    def _show_result(
        self, icon_name: str, title: str, description: str, is_error: bool
    ):
        """Transition from progress to a result page."""
        page_name = self._build_result_page(icon_name, title, description, is_error)
        self.stack.set_visible_child_name(page_name)
        self.win.set_title(title)
        set_label(self.win, title)

        full_msg = f"{title}. {description}" if description else title
        announce(self.win, full_msg, assertive=is_error)

        # Focus the close button
        result_box = self.stack.get_child_by_name(page_name)
        if result_box:
            child = result_box.get_last_child()
            if child:
                btn = child.get_child() if hasattr(child, "get_child") else None
                if btn:
                    btn.grab_focus()

    def _on_close_request(self, _win):
        self._cancelled = True
        return False

    def _start_verification(self):
        threading.Thread(target=self._verify_thread, daemon=True).start()

    def _verify_thread(self):
        try:
            clear_state()
            outcome = verify_iso(
                is_cancelled=lambda: self._cancelled,
                progress=lambda percentage, filename: GLib.idle_add(
                    self._update_progress,
                    percentage,
                    _("Checking the file: {filename}").format(filename=filename),
                    filename,
                ),
            )
            if outcome.status is VerificationStatus.SUCCESS:
                write_state("verified")
                GLib.idle_add(self._on_success)
            elif outcome.status is VerificationStatus.CANCELLED:
                write_state("failed")
                GLib.idle_add(self._on_cancelled)
            else:
                self._has_failure = True
                write_state("failed")
                GLib.idle_add(self._on_error, outcome.reason)
        except OSError as error:
            self._has_failure = True
            GLib.idle_add(self._on_error, str(error))

    def _update_progress(self, pct: int, status: str, filename: str):
        self.progress.set_fraction(pct / 100.0)
        self.file_label.set_text(filename)
        self.status_page.set_description(status)
        announce(self.win, status)

    def _on_error(self, reason: str):
        logging.getLogger(__name__).error("Integrity verification failed: %s", reason)
        self._show_result(
            "dialog-error-symbolic",
            _("Verification failed"),
            _(
                "The live media could not be verified. Download the system again or use another USB drive."
            ),
            is_error=True,
        )

    def _on_success(self):
        self._show_result(
            "emblem-ok-symbolic",
            _("Verification complete"),
            _("The files are intact."),
            is_error=False,
        )

    def _on_cancelled(self):
        self._show_result(
            "dialog-warning-symbolic",
            _("Verification canceled"),
            _("The integrity check was not completed."),
            is_error=False,
        )


def verify_headless() -> int:
    """Run verification without GUI. Returns 0 on success, 1 on failure."""
    try:
        clear_state()
        outcome = verify_iso()
        state = "verified" if outcome.status is VerificationStatus.SUCCESS else "failed"
        write_state(state)
        return 0 if outcome.status is VerificationStatus.SUCCESS else 1
    except OSError:
        return 1


def main() -> int:
    signal.signal(signal.SIGINT, lambda *_: sys.exit(1))

    try:
        if state_is_verified():
            return 0
        lock_descriptor = acquire_lock()
    except OSError:
        return 1
    if lock_descriptor is None:
        return 1
    try:
        if state_is_verified():
            return 0

        # Headless mode: run verification without GUI
        if "--no-gui" in sys.argv:
            return verify_headless()

        Adw.init()
        app = VerifyApp()
        app.run([])
        return 0 if state_is_verified() else 1
    finally:
        os.close(lock_descriptor)


if __name__ == "__main__":
    sys.exit(main())
