from __future__ import annotations

import pytest
from pathlib import Path
from unittest.mock import patch

from leadtech_bmad_mcp.triage import (
    parse_capitalisation_entries,
    novelty_level,
    scope_level,
    CapitalisationEntry,
)
from leadtech_bmad_mcp.knowledge import LeadtechPaths


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

VALID_BLOCK = """\
2026-03-15 — app-alexandrie

FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/backend/patterns/nestjs.md

Pourquoi :
L'ordre des guards NestJS a causé request.user undefined dans EmailVerifiedGuard.

Proposition :
Toujours enregistrer AuthGuard en premier dans providers[] avant tout guard qui lit request.user.
"""

VALID_BLOCK_2 = """\
2026-03-20 — app-foo

FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/frontend/risques/general.md

Pourquoi :
Les useEffect sans deps array causent des boucles infinies.

Proposition :
Toujours spécifier le tableau de dépendances dans useEffect.
"""

SEPARATOR = "\n\n"


# ---------------------------------------------------------------------------
# parse_capitalisation_entries
# ---------------------------------------------------------------------------

def test_parse_single_entry():
    entries = parse_capitalisation_entries(VALID_BLOCK)
    assert len(entries) == 1
    e = entries[0]
    assert "app-alexandrie" in e.header
    assert e.target == "knowledge/backend/patterns/nestjs.md"
    assert "guards" in e.why.lower()
    assert "AuthGuard" in e.proposal


def test_parse_multiple_entries():
    raw = VALID_BLOCK + SEPARATOR + VALID_BLOCK_2
    entries = parse_capitalisation_entries(raw)
    assert len(entries) == 2
    assert "app-alexandrie" in entries[0].header
    assert "app-foo" in entries[1].header


def test_parse_empty_returns_empty():
    entries = parse_capitalisation_entries("")
    assert entries == []


def test_parse_malformed_no_match():
    raw = "Ce texte ne contient aucune entrée valide."
    entries = parse_capitalisation_entries(raw)
    assert entries == []


def test_parse_preserves_multiline_proposal():
    raw = """\
2026-03-15 — proj

FILE_UPDATE_PROPOSAL
Fichier cible : knowledge/backend/patterns/auth.md

Pourquoi :
Raison courte.

Proposition :
Ligne 1 du pattern.
Ligne 2 du pattern.
Ligne 3 du pattern.
"""
    entries = parse_capitalisation_entries(raw)
    assert len(entries) == 1
    assert "Ligne 2" in entries[0].proposal
    assert "Ligne 3" in entries[0].proposal


# ---------------------------------------------------------------------------
# novelty_level
# ---------------------------------------------------------------------------

def _make_paths_with_target(tmp_path: Path, target_rel: str, content: str) -> LeadtechPaths:
    target = tmp_path / target_rel
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(content, encoding="utf-8")
    return LeadtechPaths(
        root=tmp_path,
        knowledge=tmp_path / "knowledge",
        capitalisation=tmp_path / "95_a_capitaliser.md",
        projects_conf=tmp_path / "_projects.conf",
    )


def test_novelty_exact_doublon(tmp_path):
    proposal = "Toujours enregistrer AuthGuard en premier dans providers[]."
    paths = _make_paths_with_target(tmp_path, "knowledge/backend/patterns/nestjs.md", proposal)
    entry = CapitalisationEntry(
        header="2026-03-15 — proj",
        target="knowledge/backend/patterns/nestjs.md",
        why="Raison.",
        proposal=proposal,
    )
    with patch("leadtech_bmad_mcp.triage.get_paths", return_value=paths):
        assert novelty_level(entry) == "DOUBLON_EXACT"


def test_novelty_semantique_doublon(tmp_path):
    # Le fichier cible contient déjà la plupart des mots clés de la proposition
    existing = "toujours enregistrer authguard premier providers avant guard request"
    paths = _make_paths_with_target(tmp_path, "knowledge/backend/patterns/nestjs.md", existing)
    entry = CapitalisationEntry(
        header="2026-03-15 — proj",
        target="knowledge/backend/patterns/nestjs.md",
        why="Raison.",
        proposal="Toujours enregistrer authguard premier providers avant guard request utilisateur",
    )
    with patch("leadtech_bmad_mcp.triage.get_paths", return_value=paths):
        result = novelty_level(entry)
    assert result == "DOUBLON_SEMANTIQUE"


def test_novelty_nouveau_when_file_missing(tmp_path):
    paths = LeadtechPaths(
        root=tmp_path,
        knowledge=tmp_path / "knowledge",
        capitalisation=tmp_path / "95_a_capitaliser.md",
        projects_conf=tmp_path / "_projects.conf",
    )
    entry = CapitalisationEntry(
        header="2026-03-15 — proj",
        target="knowledge/backend/patterns/nestjs.md",
        why="Raison.",
        proposal="Un pattern totalement nouveau sur les migrations Prisma.",
    )
    with patch("leadtech_bmad_mcp.triage.get_paths", return_value=paths):
        assert novelty_level(entry) == "NOUVEAU"


def test_novelty_nouveau_when_different_content(tmp_path):
    existing = "Ce fichier parle de Stripe webhooks et idempotency."
    paths = _make_paths_with_target(tmp_path, "knowledge/backend/patterns/stripe.md", existing)
    entry = CapitalisationEntry(
        header="2026-03-15 — proj",
        target="knowledge/backend/patterns/stripe.md",
        why="Raison.",
        proposal="Un pattern sur les guards NestJS complètement différent.",
    )
    with patch("leadtech_bmad_mcp.triage.get_paths", return_value=paths):
        assert novelty_level(entry) == "NOUVEAU"


# ---------------------------------------------------------------------------
# scope_level
# ---------------------------------------------------------------------------

def test_scope_projet_when_multiple_markers():
    entry = CapitalisationEntry(
        header="2026-03-15 — proj",
        target="knowledge/frontend/patterns/forms.md",
        why="Ce composant spécifique sur cet écran particulier.",
        proposal="Le label de ce screen doit être en majuscules.",
    )
    assert scope_level(entry) == "PROJET"


def test_scope_global_when_no_markers():
    entry = CapitalisationEntry(
        header="2026-03-15 — proj",
        target="knowledge/backend/patterns/auth.md",
        why="Pattern de validation JWT applicable à tous les projets.",
        proposal="Toujours vérifier l'expiration du token côté serveur.",
    )
    assert scope_level(entry) == "GLOBAL"


def test_scope_global_when_one_marker_only():
    # un seul marker → pas suffisant pour PROJET
    entry = CapitalisationEntry(
        header="2026-03-15 — proj",
        target="knowledge/backend/patterns/auth.md",
        why="Ce route spécifique pose problème.",
        proposal="Ajouter un middleware de validation générique.",
    )
    assert scope_level(entry) == "GLOBAL"
