"""Tests for the formality (şekli eksiklik) pre-scanner.

Deterministik kuralları doğrudan, endpoint'i ASGI üzerinden test eder.
Yabancı terim LLM katmanı için `check_terms=False` kullanılır — bu
testler ağ çağrısı yapmaz.
"""

from __future__ import annotations

import pytest
from httpx import ASGITransport, AsyncClient

from app.main import app
from app.services.formality.models import Severity
from app.services.formality.rules import (
    check_abstract_has_title,
    check_abstract_length,
    check_claims_multi_dependency,
    check_claims_numbering,
    check_claims_single_sentence,
    check_claims_subheadings,
    run_deterministic_checks,
)

# --- Örnek metinler ---------------------------------------------------------

TITLE = "POST-KUANTUM KRİPTOGRAFİ DESTEKLİ YEREL AĞ GEÇİDİ SİSTEMİ"

CLEAN_CLAIMS = (
    "İSTEMLER\n"
    "1. Bir veri işleme yöntemi olup özelliği; bir girdi alınması ve "
    "işlenmesidir.\n"
    "2. İstem 1'e göre yöntem olup özelliği; girdinin şifrelenmesidir.\n"
    "3. İstem 1 veya 2'ye göre yöntem olup özelliği; sonucun "
    "saklanmasıdır.\n"
)

SUBHEADING_CLAIMS = (
    "İSTEMLER\n"
    "Bağımsız İstem 1 — Yöntem\n"
    "1. Bir yöntem olup özelliği; X içermesidir.\n"
    "Bağımlı İstemler — Yönteme Bağlı\n"
    "2. İstem 1'e göre yöntem olup özelliği; Y içermesidir.\n"
)

CLEAN_ABSTRACT = (
    "ÖZET\n"
    f"{TITLE}\n"
    "Buluş, bir veri işleme yöntemi ve sistemi ile ilgilidir."
)


# --- Deterministik kural birim testleri ------------------------------------


def test_clean_claims_have_no_subheading_finding() -> None:
    assert check_claims_subheadings(CLEAN_CLAIMS) == []


def test_subheading_claims_flagged_as_error() -> None:
    findings = check_claims_subheadings(SUBHEADING_CLAIMS)
    assert len(findings) == 1
    assert findings[0].rule_id == "claims.no_subheadings"
    assert findings[0].severity == Severity.ERROR


def test_claim_numbering_gap_detected() -> None:
    claims = "1. Birinci istem.\n3. Üçüncü istem — iki atlandı."
    findings = check_claims_numbering(claims)
    assert any(f.rule_id == "claims.numbering_gap" for f in findings)


def test_claim_numbering_must_start_at_one() -> None:
    claims = "2. İkinci istemden başlıyor."
    findings = check_claims_numbering(claims)
    assert any(f.rule_id == "claims.numbering_start" for f in findings)


def test_clean_claims_numbering_ok() -> None:
    assert check_claims_numbering(CLEAN_CLAIMS) == []


def test_bad_multi_dependency_detected() -> None:
    claims = "5. İstem 1 ila 3'teki gibi bir cihaz olup özelliği; Z'dir."
    findings = check_claims_multi_dependency(claims)
    assert len(findings) == 1
    assert findings[0].rule_id == "claims.bad_multi_dependency"


def test_good_multi_dependency_not_flagged() -> None:
    claims = "5. İstem 3 veya 4'teki gibi bir cihaz olup özelliği; Z'dir."
    assert check_claims_multi_dependency(claims) == []


def test_multi_sentence_claim_flagged() -> None:
    claims = (
        "1. Bir yöntem olup özelliği; X içermesidir. Ayrıca Y de içerir."
    )
    findings = check_claims_single_sentence(claims)
    assert any(f.rule_id == "claims.multi_sentence" for f in findings)


def test_single_sentence_claim_ok() -> None:
    claims = "1. Bir yöntem olup özelliği; X, Y ve Z içermesidir."
    assert check_claims_single_sentence(claims) == []


def test_abstract_missing_title_flagged() -> None:
    findings = check_abstract_has_title("Buluş bir yöntemdir.", TITLE)
    assert len(findings) == 1
    assert findings[0].rule_id == "abstract.missing_title"
    assert findings[0].severity == Severity.ERROR


def test_abstract_with_title_ok() -> None:
    assert check_abstract_has_title(CLEAN_ABSTRACT, TITLE) == []


def test_abstract_too_long_flagged() -> None:
    long_abstract = " ".join(["kelime"] * 200)
    findings = check_abstract_length(long_abstract)
    assert len(findings) == 1
    assert findings[0].rule_id == "abstract.too_long"


def test_abstract_within_limit_ok() -> None:
    short_abstract = " ".join(["kelime"] * 120)
    assert check_abstract_length(short_abstract) == []


def test_run_deterministic_checks_clean_input() -> None:
    """Temiz bir başvuruda hiç ERROR olmamalı."""
    findings = run_deterministic_checks(
        description=f"TARİFNAME\n{TITLE}\nBuluş bir yöntemle ilgilidir.",
        claims=CLEAN_CLAIMS,
        abstract=CLEAN_ABSTRACT,
        invention_title=TITLE,
    )
    errors = [f for f in findings if f.severity == Severity.ERROR]
    assert errors == []


def test_run_deterministic_checks_catches_all_four_objections() -> None:
    """PQC Gateway itirazındaki 4 eksikliğin tespit edilebilirliği."""
    findings = run_deterministic_checks(
        claims=SUBHEADING_CLAIMS,
        abstract="Buluş bir yöntemdir.",  # başlık yok
        invention_title=TITLE,
    )
    rule_ids = {f.rule_id for f in findings}
    # Eksiklik 1: alt başlık
    assert "claims.no_subheadings" in rule_ids
    # Eksiklik 2: özette buluş başlığı yok
    assert "abstract.missing_title" in rule_ids


# --- Endpoint testleri ------------------------------------------------------


@pytest.mark.asyncio
async def test_formality_endpoint_clean_input() -> None:
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        resp = await client.post(
            "/api/v1/formality/check",
            json={
                "description": f"TARİFNAME\n{TITLE}\nBuluş bir yöntemle ilgilidir.",
                "claims": CLEAN_CLAIMS,
                "abstract": CLEAN_ABSTRACT,
                "invention_title": TITLE,
                "check_terms": False,
            },
        )
    assert resp.status_code == 200
    body = resp.json()
    assert body["error_count"] == 0
    assert body["checked_terms"] is False


@pytest.mark.asyncio
async def test_formality_endpoint_flags_errors() -> None:
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        resp = await client.post(
            "/api/v1/formality/check",
            json={
                "claims": SUBHEADING_CLAIMS,
                "abstract": "Buluş bir yöntemdir.",
                "invention_title": TITLE,
                "check_terms": False,
            },
        )
    assert resp.status_code == 200
    body = resp.json()
    assert body["error_count"] >= 2
    # Bulgular ciddiyet sırasına göre sıralı: ilk bulgu error olmalı.
    assert body["findings"][0]["severity"] == "error"


@pytest.mark.asyncio
async def test_formality_endpoint_empty_input() -> None:
    """Boş girdi 200 dönmeli, çökmemeli."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        resp = await client.post(
            "/api/v1/formality/check",
            json={"check_terms": False},
        )
    assert resp.status_code == 200


# --- EPO (Avrupa Patent Ofisi) kuralları -----------------------------------


def test_epo_abstract_missing_flagged() -> None:
    from app.services.formality import Jurisdiction, run_deterministic_checks

    findings = run_deterministic_checks(
        claims="1. A method comprising step X.",
        abstract="",
        jurisdiction=Jurisdiction.EP,
    )
    rule_ids = {f.rule_id for f in findings}
    assert "epo.abstract_missing" in rule_ids


def test_epo_claims_missing_flagged() -> None:
    from app.services.formality import Jurisdiction, run_deterministic_checks

    findings = run_deterministic_checks(
        claims="",
        abstract="An apparatus for processing data.",
        jurisdiction=Jurisdiction.EP,
    )
    assert any(f.rule_id == "epo.claims_missing" for f in findings)


def test_epo_abstract_too_long_flagged() -> None:
    from app.services.formality import Jurisdiction, run_deterministic_checks

    findings = run_deterministic_checks(
        claims="1. A device.",
        abstract=" ".join(["word"] * 160),
        jurisdiction=Jurisdiction.EP,
    )
    epo = [f for f in findings if f.rule_id == "epo.abstract_too_long"]
    assert len(epo) == 1
    assert epo[0].severity == Severity.WARNING


def test_epo_abstract_merit_language_flagged() -> None:
    from app.services.formality import Jurisdiction, run_deterministic_checks

    findings = run_deterministic_checks(
        claims="1. A device.",
        abstract="This invention is superior to existing systems.",
        jurisdiction=Jurisdiction.EP,
    )
    assert any(f.rule_id == "epo.abstract_merit_language" for f in findings)


def test_epo_clean_application_no_errors() -> None:
    from app.services.formality import Jurisdiction, run_deterministic_checks
    from app.services.formality import Severity as Sev

    findings = run_deterministic_checks(
        claims="1. A method comprising receiving an input and processing it.",
        abstract="A method for processing data inputs in a computing system.",
        jurisdiction=Jurisdiction.EP,
    )
    assert [f for f in findings if f.severity == Sev.ERROR] == []


# --- USPTO kuralları --------------------------------------------------------


def test_uspto_abstract_too_long_is_error() -> None:
    """USPTO'da 150 kelime kesin sınır — ERROR seviyesinde."""
    from app.services.formality import Jurisdiction, run_deterministic_checks

    findings = run_deterministic_checks(
        claims="1. A device.",
        abstract=" ".join(["word"] * 160),
        jurisdiction=Jurisdiction.US,
    )
    us = [f for f in findings if f.rule_id == "uspto.abstract_too_long"]
    assert len(us) == 1
    assert us[0].severity == Severity.ERROR


def test_uspto_abstract_prior_art_flagged() -> None:
    from app.services.formality import Jurisdiction, run_deterministic_checks

    findings = run_deterministic_checks(
        claims="1. A device.",
        abstract="Unlike conventional prior art systems, this device is faster.",
        jurisdiction=Jurisdiction.US,
    )
    assert any(f.rule_id == "uspto.abstract_prior_art" for f in findings)


def test_uspto_abstract_missing_flagged() -> None:
    from app.services.formality import Jurisdiction, run_deterministic_checks

    findings = run_deterministic_checks(
        claims="1. A device.",
        abstract="",
        jurisdiction=Jurisdiction.US,
    )
    assert any(f.rule_id == "uspto.abstract_missing" for f in findings)


def test_uspto_no_dependent_claims_info() -> None:
    from app.services.formality import Jurisdiction, run_deterministic_checks

    findings = run_deterministic_checks(
        claims="1. A first device.\n2. A second device.",
        abstract="An abstract describing two devices.",
        jurisdiction=Jurisdiction.US,
    )
    assert any(f.rule_id == "uspto.no_dependent_claims" for f in findings)


# --- Jurisdiction izolasyonu ------------------------------------------------


def test_tr_specific_rules_not_applied_to_epo() -> None:
    """TR'ye özgü 'İSTEMLER başlığı yok' kuralı EP taramasında çıkmamalı."""
    from app.services.formality import Jurisdiction, run_deterministic_checks

    findings = run_deterministic_checks(
        claims="1. A method comprising X.",
        abstract="A technical summary.",
        jurisdiction=Jurisdiction.EP,
    )
    rule_ids = {f.rule_id for f in findings}
    assert "istemler.missing_heading" not in rule_ids
    assert "abstract.missing_title" not in rule_ids


def test_common_claim_rules_apply_to_all_jurisdictions() -> None:
    """İstem alt başlığı kuralı her ofiste geçerli (ortak kural)."""
    from app.services.formality import Jurisdiction, run_deterministic_checks

    for juris in (Jurisdiction.TR, Jurisdiction.EP, Jurisdiction.US):
        findings = run_deterministic_checks(
            claims="Bağımsız İstem 1 — Yöntem\n1. Test claim.",
            abstract="x",
            jurisdiction=juris,
        )
        assert any(
            f.rule_id == "claims.no_subheadings" for f in findings
        ), juris


@pytest.mark.asyncio
async def test_formality_endpoint_accepts_jurisdiction() -> None:
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as client:
        resp = await client.post(
            "/api/v1/formality/check",
            json={
                "claims": "1. A device.",
                "abstract": "",
                "jurisdiction": "EP",
                "check_terms": False,
            },
        )
    assert resp.status_code == 200
    rule_ids = {f["rule_id"] for f in resp.json()["findings"]}
    assert "epo.abstract_missing" in rule_ids
