"""Couche logique de l'éditeur de masques en fenêtre dédiée (sans display).

Ces tests valident le modèle pur (rectangles/pages/conversions/templates) qui
sous-tend `gui_v6/mask_editor_window.py`. La fenêtre Tk elle-même est couverte par
un smoke headless séparé.
"""

from __future__ import annotations

import json

import pytest

from gui_v6.mask_editor_model import (
    ZOOM_MAX,
    ZOOM_MIN,
    MaskEditorModel,
    clamp_page,
    clamp_zoom,
    fit_page_zoom,
    fit_width_zoom,
    pdf_to_screen,
    screen_to_pdf,
)


# --- Conversions / clamps ---------------------------------------------------

def test_clamp_zoom_bounds():
    assert clamp_zoom(0.001) == ZOOM_MIN
    assert clamp_zoom(999.0) == ZOOM_MAX
    assert clamp_zoom(1.0) == 1.0


def test_clamp_page_bounds():
    assert clamp_page(-3, 5) == 0
    assert clamp_page(10, 5) == 4
    assert clamp_page(2, 5) == 2
    assert clamp_page(0, 0) == 0  # aucun document


def test_screen_pdf_roundtrip_applies_zoom():
    assert screen_to_pdf(200.0, 100.0, zoom=2.0) == (100.0, 50.0)
    assert pdf_to_screen(100.0, 50.0, zoom=2.0) == (200.0, 100.0)


def test_fit_width_zoom():
    assert fit_width_zoom(595.0, 1190.0, padding=0.0) == 2.0


def test_fit_page_zoom_uses_min_ratio():
    # ratio largeur = 2.0, ratio hauteur = 1.0 -> on prend le plus petit
    assert fit_page_zoom(595.0, 842.0, 1190.0, 842.0, padding=0.0) == 1.0


# --- Modèle rectangles / pages ---------------------------------------------

def test_add_rect_normalizes_corners():
    m = MaskEditorModel()
    r = m.add_rect(page=0, x0=100, y0=80, x1=40, y1=20)
    assert r is not None
    assert (r.x0, r.y0, r.x1, r.y1) == (40.0, 20.0, 100.0, 80.0)
    assert r.page == 0
    assert m.count_total() == 1


def test_add_rect_ignores_tiny_rectangles():
    m = MaskEditorModel()
    assert m.add_rect(page=0, x0=10, y0=10, x1=11, y1=11) is None
    assert m.count_total() == 0


def test_rect_at_returns_topmost_then_none():
    m = MaskEditorModel()
    a = m.add_rect(0, 0, 0, 100, 100)
    b = m.add_rect(0, 50, 50, 150, 150)
    assert m.rect_at(0, 60, 60) is b   # zone de recouvrement -> dernier dessiné
    assert m.rect_at(0, 10, 10) is a   # seulement dans a
    assert m.rect_at(0, 500, 500) is None
    assert m.rect_at(1, 60, 60) is None  # autre page


def test_delete_rect():
    m = MaskEditorModel()
    a = m.add_rect(0, 0, 0, 50, 50)
    assert m.delete_rect(a) is True
    assert m.count_total() == 0
    assert m.delete_rect(a) is False


def test_clear_page_and_clear_all():
    m = MaskEditorModel()
    m.add_rect(0, 0, 0, 50, 50)
    m.add_rect(0, 60, 60, 90, 90)
    m.add_rect(1, 0, 0, 50, 50)
    assert m.count_page(0) == 2
    assert m.clear_page(0) == 2
    assert m.count_page(0) == 0
    assert m.count_total() == 1
    assert m.clear_all() == 1
    assert m.count_total() == 0


def test_set_page_clamps_and_updates_index():
    m = MaskEditorModel(page_count=3)
    assert m.set_page(5) == 2
    assert m.page_index == 2
    assert m.set_page(-1) == 0
    assert m.page_index == 0


# --- Templates (compat moteur / pdf_mask_designer) --------------------------

def test_payload_shape_and_roundtrip():
    m = MaskEditorModel(page_count=1, page_sizes=[(595.0, 842.0)], name="demo")
    m.add_rect(0, 10, 20, 110, 60, label="NOM")
    payload = m.to_payload()
    assert payload["version"] == 1
    assert payload["name"] == "demo"
    assert payload["page_size"] == {"width": 595.0, "height": 842.0}
    assert payload["masks"][0] == {
        "page": 0,
        "x0": 10.0,
        "y0": 20.0,
        "x1": 110.0,
        "y1": 60.0,
        "label": "NOM",
    }
    # le payload doit survivre à une sérialisation JSON
    payload2 = json.loads(json.dumps(payload))
    m2 = MaskEditorModel.from_payload(payload2)
    assert m2.name == "demo"
    assert m2.count_total() == 1
    r = m2.masks_for_page(0)[0]
    assert (r.x0, r.y0, r.x1, r.y1, r.label) == (10.0, 20.0, 110.0, 60.0, "NOM")


def test_payload_is_compatible_with_pdf_mask_designer_template():
    from pdf_mask_designer import Template

    m = MaskEditorModel(page_count=1, page_sizes=[(595.0, 842.0)], name="demo")
    m.add_rect(0, 10, 20, 110, 60, label="NOM")
    tpl = Template.from_dict(m.to_payload())
    assert tpl.name == "demo"
    assert tpl.page_size == (595.0, 842.0)
    assert len(tpl.masks) == 1
    assert tpl.masks[0].label == "NOM"


# --- Smoke headless de la fenêtre Tk (skip si pas de display) ---------------

@pytest.fixture
def ctk_root():
    ctk = pytest.importorskip("customtkinter")
    try:
        root = ctk.CTk()
    except Exception as exc:  # pas de display disponible
        pytest.skip(f"display Tk indisponible: {exc}")
    root.withdraw()
    try:
        yield root
    finally:
        try:
            root.destroy()
        except Exception:
            pass


def test_window_smoke_scrollbars_add_select_delete_save(ctk_root, tmp_path):
    from gui_v6.mask_editor_window import MaskEditorWindow

    win = MaskEditorWindow(ctk_root, templates_dir=tmp_path)
    win.update_idletasks()

    # 1) la fenêtre expose bien deux scrollbars (le manque qui rendait l'éditeur inutilisable)
    assert win.has_scrollbars()

    # 2) ajout d'un rectangle (sur l'aperçu d'exemple, sans PDF réel)
    win.model.page_count = 1
    win.model.page_sizes = [(595.0, 842.0)]
    rect = win.add_mask_rect_pdf(0, 30, 40, 200, 120, label="NOM")
    assert rect is not None
    assert win.model.count_total() == 1

    # 3) sauvegarde du template (JSON) -> relisible et compatible
    out = win.save_template_to(tmp_path / "demo.json")
    assert out.exists()
    reloaded = json.loads(out.read_text(encoding="utf-8"))
    assert reloaded["masks"][0]["label"] == "NOM"

    # 4) sélection + suppression individuelle
    win.select_rect(win.model.masks_for_page(0)[0])
    win.delete_selected()
    assert win.model.count_total() == 0

    win.destroy()


def test_window_smoke_load_template_roundtrip(ctk_root, tmp_path):
    from gui_v6.mask_editor_window import MaskEditorWindow

    win = MaskEditorWindow(ctk_root, templates_dir=tmp_path)
    win.model.page_count = 1
    win.model.page_sizes = [(595.0, 842.0)]
    win.add_mask_rect_pdf(0, 10, 20, 110, 60, label="ADRESSE")
    path = win.save_template_to(tmp_path / "rt.yml")

    win.clear_all()
    assert win.model.count_total() == 0

    win.load_template_path(path)
    assert win.model.count_total() == 1
    assert win.model.masks_for_page(0)[0].label == "ADRESSE"
    win.destroy()


def test_config_tab_launches_dedicated_window(ctk_root, tmp_path, monkeypatch):
    """Garde-fou de recâblage : l'onglet Configuration construit sans l'éditeur
    encastré et ouvre bien la fenêtre dédiée (avec scrollbars)."""
    from gui_v6.tabs import tab_config

    monkeypatch.setattr(tab_config, "_app_base_dir", lambda: tmp_path)
    tab = tab_config.ConfigTab(ctk_root)
    tab.update_idletasks()

    # plus d'éditeur encastré
    assert not hasattr(tab, "_mask_canvas")

    tab._open_full_mask_editor()
    win = tab._mask_editor_window
    assert win is not None
    assert win.has_scrollbars()

    # un template sauvé via la fenêtre rafraîchit la sélection côté Réglages
    win.model.page_count = 1
    win.model.page_sizes = [(595.0, 842.0)]
    win.add_mask_rect_pdf(0, 10, 20, 110, 60, label="NOM")
    saved = win.save_template_to(tmp_path / "config" / "mask_templates" / "depuis_fenetre.json")
    assert saved.exists()
    # A14 : sauver depuis la fenêtre active le template comme masque manuel du run
    assert tab._state.manual_mask_template == saved

    win.destroy()
    tab.destroy()


def test_window_loads_initial_template(ctk_root, tmp_path):
    """B5 / continuité : un template fourni à l'ouverture est chargé (l'utilisateur
    reprend son travail au lieu de repartir d'une page vierge)."""
    from gui_v6.mask_editor_window import MaskEditorWindow

    seed = MaskEditorModel(page_count=1, page_sizes=[(595.0, 842.0)], name="seed")
    seed.add_rect(0, 5, 5, 90, 40, label="IPP")
    path = tmp_path / "seed.json"
    path.write_text(json.dumps(seed.to_payload(), ensure_ascii=False), encoding="utf-8")

    win = MaskEditorWindow(ctk_root, templates_dir=tmp_path, initial_template=path)
    assert win.model.count_total() == 1
    assert win.model.masks_for_page(0)[0].label == "IPP"
    win.destroy()
