"""Shell de la GUI V6 (G4 — alignement maquette).

Reproduit l'identité de ``docs/ui_mockup_v6.html`` : shell étroit, header avec
identité produit + version + statut licence + liseré accent, barre d'onglets
custom (pas CTkTabview brut), navigation par panneaux mis en cache après leur
première ouverture visible, changement de thème à chaud. La logique (runner
moteur, config, licence) est inchangée.

La fenêtre n'est créée qu'à l'instanciation de :class:`AnonymisationApp`.
"""

from __future__ import annotations

import os
from typing import Optional

import customtkinter as ctk

from gui_v6 import theme as theme_mod
from gui_v6 import ui_kit
from gui_v6.config_state import ConfigState
from gui_v6.license_client import LicenseClient, LicenseStatus
from gui_v6.machine_id import default_machine_id
from gui_v6.tabs.tab_about import AboutTab
from gui_v6.tabs.tab_config import ConfigTab
from gui_v6.tabs.tab_usage import UsageTab

DEFAULT_PORTAL_URL = "https://app.aivanov.eu"


def resolve_portal_url() -> str:
    """URL du portail : env ``ANON_PORTAL_URL`` sinon défaut prod."""
    return os.environ.get("ANON_PORTAL_URL", DEFAULT_PORTAL_URL)


def bound_local_status(status: LicenseStatus, local_machine_id: str) -> LicenseStatus:
    """Annoter le statut licence selon le binding poste.

    Souple (décision D1) : on N'EMPÊCHE PAS le traitement. Si la licence locale
    est valide mais liée à un autre ``machine_id`` que le poste courant (ex.
    ``license.json`` copié), on le **signale** par un statut non valide d'affichage.
    """
    if status.valid and status.machine_id and status.machine_id != local_machine_id:
        return LicenseStatus(
            valid=False,
            status="autre_poste",
            message="Licence liée à un autre poste",
            expires_at=status.expires_at,
            grace_days=status.grace_days,
            machine_id=status.machine_id,
            license_ref=status.license_ref,
        )
    return status


_TABS = [
    ("use", "📄 Utilisation"),
    ("cfg", "⚙️ Administration"),
    ("about", "ℹ️ À propos"),
]


class AnonymisationApp(ctk.CTk):
    def __init__(
        self,
        license_client: Optional[LicenseClient] = None,
        theme_name: str = theme_mod.DEFAULT_THEME,
    ) -> None:
        super().__init__()
        self._theme_name = theme_name
        self._license_client = license_client or LicenseClient(resolve_portal_url())
        self._config = ConfigState()
        self._active = "use"
        self._tab_buttons: dict = {}
        self._tab_frames: dict = {}
        self._visible_tab = None

        self.title("Pseudonymisation de vos documents — bêta")
        self.geometry("820x880")
        self.minsize(720, 680)
        self._render()

    # -- thème / rendu ----------------------------------------------------

    def set_theme(self, name: str) -> None:
        self._theme_name = name
        self._render()

    def _render(self) -> None:
        self._palette = theme_mod.apply_theme(self._theme_name)
        p = self._palette
        try:
            self.configure(fg_color=p["bg"])
        except Exception:
            pass
        for child in self.winfo_children():
            child.destroy()
        # Les frames d'onglets mis en cache étaient des enfants détruits ci-dessus :
        # on vide le cache pour que ``_show`` recrée proprement l'onglet actif
        # (sinon on re-packe un widget mort → onglet vide / TclError au changement de thème).
        self._tab_frames = {}
        self._visible_tab = None
        self._build_header(p)
        self._build_tabsbar(p)
        self._content = ctk.CTkScrollableFrame(self, fg_color=p["bg"])
        self._content.pack(fill="both", expand=True)
        self._show(self._active)

    # -- header -----------------------------------------------------------

    def _safe_local_status(self) -> LicenseStatus:
        try:
            status = self._license_client.local_status()
            return bound_local_status(status, default_machine_id())
        except Exception:
            return LicenseStatus.unavailable()

    def _build_header(self, p: dict) -> None:
        header = ctk.CTkFrame(self, fg_color=p["card"], corner_radius=0)
        header.pack(fill="x")
        identity = ctk.CTkFrame(header, fg_color="transparent")
        identity.pack(side="left", padx=16, pady=10)
        ctk.CTkLabel(
            identity, text="🛡️  aivanonym", text_color=p["text"], font=ui_kit.font(18, "bold")
        ).pack(side="left")
        ctk.CTkLabel(
            identity,
            text="bêta",
            text_color="#ffffff",
            fg_color=p["primary"],
            corner_radius=8,
            font=ui_kit.font(10, "bold"),
        ).pack(side="left", padx=(8, 0), ipadx=6, ipady=1)

        status = self._safe_local_status()
        ctk.CTkLabel(
            header,
            text=f"licence : {status.status}",
            text_color=theme_mod.status_color(self._theme_name, status.status),
            font=ui_kit.font(11),
        ).pack(side="right", padx=(8, 16))
        ctk.CTkLabel(
            header, text="v6.0", text_color=p["text_muted"], font=ui_kit.font(11)
        ).pack(side="right", padx=4)

        # Liseré accent sous le header (border-bottom 3px primary).
        ctk.CTkFrame(self, fg_color=p["primary"], height=3, corner_radius=0).pack(fill="x")

    # -- barre d'onglets --------------------------------------------------

    def _build_tabsbar(self, p: dict) -> None:
        bar = ctk.CTkFrame(self, fg_color=p["card"], corner_radius=0)
        bar.pack(fill="x")
        self._tab_buttons = {}
        for key, label in _TABS:
            active = key == self._active
            btn = ctk.CTkButton(
                bar,
                text=label,
                command=lambda k=key: self._show(k),
                fg_color="transparent",
                hover_color=p["card_border"],
                text_color=p["primary"] if active else p["text_dim"],
                font=ui_kit.font(13, "bold" if active else "normal"),
                corner_radius=0,
                width=10,
            )
            btn.pack(side="left", padx=4, pady=4)
            self._tab_buttons[key] = btn

    def _refresh_tabbar(self) -> None:
        p = self._palette
        for key, btn in self._tab_buttons.items():
            active = key == self._active
            btn.configure(
                text_color=p["primary"] if active else p["text_dim"],
                font=ui_kit.font(13, "bold" if active else "normal"),
            )

    # -- contenu ----------------------------------------------------------

    def _create_tab(self, key: str):
        p = self._palette
        status = self._safe_local_status()
        if key == "use":
            return UsageTab(
                self._content,
                palette=p,
                config_provider=lambda: self._config,
                on_theme_change=self.set_theme,
                current_theme=self._theme_name,
                usage_reporter=self._report_usage,
            )
        if key == "cfg":
            return ConfigTab(self._content, palette=p, state=self._config)
        return AboutTab(
            self._content,
            palette=p,
            status=status,
            theme_name=self._theme_name,
            license_client=self._license_client,
        )

    # -- télémétrie d'usage -----------------------------------------------

    def _usage_session(self):
        if getattr(self, "_usage_http_session", None) is None:
            try:
                import requests

                self._usage_http_session = requests.Session()
            except Exception:
                self._usage_http_session = None
        return self._usage_http_session

    def _report_usage(self, summary) -> None:
        """Envoie la télémétrie d'usage en fin de run (non bloquant, best-effort).

        N'envoie rien si aucune licence locale valide. Ne lève jamais.
        """
        try:
            from gui_v6 import __version__ as gui_version
            from gui_v6 import usage_telemetry
            from gui_v6.machine_id import default_machine_id

            session = self._usage_session()
            if session is None:
                return
            status = self._safe_local_status()
            base_url = getattr(self._license_client, "_base_url", "") or resolve_portal_url()
            usage_telemetry.report_run_summary(
                summary,
                base_url=base_url,
                license_ref=getattr(status, "license_ref", None),
                machine_id=default_machine_id(),
                session=session,
                app_name="gui_v6",
                app_version=gui_version,
            )
        except Exception:
            pass

    def _show(self, key: str) -> None:
        self._active = key
        self._refresh_tabbar()
        if self._visible_tab is not None:
            self._tab_frames[self._visible_tab].pack_forget()
        if key not in self._tab_frames:
            self._tab_frames[key] = self._create_tab(key)
        self._tab_frames[key].pack(fill="both", expand=True)
        self._visible_tab = key
