"""Şekli eksiklik tarayıcısının deterministik (LLM'siz) kuralları.

Her kural, başvuru metinlerini regex/string analiziyle inceler ve sıfır
veya daha fazla `Finding` döndürür. LLM gerektirmez — ücretsiz, anında ve
%100 tekrarlanabilir.

Kurallar TürkPatent Patent Başvuru Kılavuzu ve 6769 sayılı SMK
Yönetmeliği'ne dayanır. Kural kimlikleri (rule_id) kararlıdır; UI ve
testler bunlara güvenir.
"""

from __future__ import annotations

import re

from app.services.formality.models import Finding, Jurisdiction, Severity

# İstemler bölümünde yasak olan alt başlık kalıpları. TürkPatent şekli
# inceleme kuralı: "İstemler bağımsız, bağımlı, yöntem ve sistem istemi
# gibi alt başlıklar şeklinde yazılmaz."
_SUBHEADING_PATTERNS = [
    re.compile(r"ba[ğg]ı?ms[ıi]z\s+istem", re.IGNORECASE),
    re.compile(r"ba[ğg]ı?ml[ıi]\s+istem", re.IGNORECASE),
    re.compile(r"^\s*(y[öo]ntem|sistem|[üu]r[üu]n|aparat|kullan[ıi]m)\s+istem",
               re.IGNORECASE | re.MULTILINE),
]

# Bir satırın istem başlangıcı olup olmadığını anlamak için: "1.", "12.",
# "1)" gibi numaralı satır başları.
_CLAIM_START = re.compile(r"^\s*(\d+)\s*[.)]\s+", re.MULTILINE)

# Kabul edilmeyen çoklu bağımlı istem ifadeleri (kılavuz sf. 8-9):
# "İstem 1 ila 3", "İstem 1 ve 3", "İstem 1-3'teki" alternatif yolla
# gönderme yapmadığı için reddedilir.
_BAD_MULTI_DEP = re.compile(
    r"[İIi]stem\s+\d+\s+(ila|ile|ve)\s+\d+", re.IGNORECASE
)


def _norm(text: str) -> str:
    """Metni karşılaştırma için sadeleştirir: küçük harf, tek boşluk."""
    return re.sub(r"\s+", " ", text or "").strip().lower()


# --------------------------------------------------------------------------
# İSTEMLER kuralları
# --------------------------------------------------------------------------

def check_claims_subheadings(claims: str) -> list[Finding]:
    """İstemler bölümünde alt başlık kullanılmamalı."""
    findings: list[Finding] = []
    for pat in _SUBHEADING_PATTERNS:
        m = pat.search(claims or "")
        if m:
            line = m.group(0).strip()
            findings.append(
                Finding(
                    rule_id="claims.no_subheadings",
                    severity=Severity.ERROR,
                    section="istemler",
                    title="İstemlerde alt başlık kullanılmış",
                    detail=(
                        "İstemler; bağımsız, bağımlı, yöntem veya sistem istemi "
                        "gibi alt başlıklar altında gruplanamaz. İstemler 1'den "
                        "başlayarak kesintisiz numaralandırılmalıdır."
                    ),
                    suggestion=(
                        f"\"{line}\" gibi alt başlıkları kaldırın; istem "
                        "metinlerini olduğu gibi bırakıp yalnızca başlık "
                        "satırlarını silin."
                    ),
                    excerpt=line,
                    legal_basis="TürkPatent Patent Başvuru Kılavuzu — İstemler",
                )
            )
            break  # tek bulgu yeterli; tekrar etme
    return findings


def check_claims_numbering(claims: str) -> list[Finding]:
    """İstemler 1, 2, 3 ... şeklinde kesintisiz numaralandırılmalı."""
    nums = [int(m.group(1)) for m in _CLAIM_START.finditer(claims or "")]
    if not nums:
        return [
            Finding(
                rule_id="claims.no_numbered_claims",
                severity=Severity.WARNING,
                section="istemler",
                title="Numaralı istem bulunamadı",
                detail=(
                    "İstemler bölümünde '1.', '2.' biçiminde numaralandırılmış "
                    "istem tespit edilemedi."
                ),
                suggestion="Her istemi sıra numarasıyla (1., 2., ...) başlatın.",
                legal_basis="TürkPatent Patent Başvuru Kılavuzu — İstemler",
            )
        ]

    findings: list[Finding] = []
    if nums[0] != 1:
        findings.append(
            Finding(
                rule_id="claims.numbering_start",
                severity=Severity.ERROR,
                section="istemler",
                title="İstem numaralandırması 1'den başlamıyor",
                detail=f"İlk istem {nums[0]} numarasıyla başlıyor; 1 olmalı.",
                suggestion="İstemleri 1'den başlayacak şekilde yeniden numaralandırın.",
                legal_basis="TürkPatent Patent Başvuru Kılavuzu — İstemler",
            )
        )
    # Kesintisiz artış kontrolü
    for prev, cur in zip(nums, nums[1:], strict=False):
        if cur != prev + 1:
            findings.append(
                Finding(
                    rule_id="claims.numbering_gap",
                    severity=Severity.ERROR,
                    section="istemler",
                    title="İstem numaralandırmasında atlama var",
                    detail=(
                        f"İstem {prev}'den sonra {cur} geliyor; "
                        f"{prev + 1} beklenir."
                    ),
                    suggestion="İstemleri kesintisiz (1, 2, 3, ...) numaralandırın.",
                    legal_basis="TürkPatent Patent Başvuru Kılavuzu — İstemler",
                )
            )
            break
    return findings


def check_claims_multi_dependency(claims: str) -> list[Finding]:
    """Çoklu bağımlı istemlerde 'İstem 1 ila 3' gibi biçimler kabul edilmez."""
    m = _BAD_MULTI_DEP.search(claims or "")
    if not m:
        return []
    return [
        Finding(
            rule_id="claims.bad_multi_dependency",
            severity=Severity.WARNING,
            section="istemler",
            title="Hatalı çoklu bağımlı istem ifadesi",
            detail=(
                f"\"{m.group(0)}\" biçimi kabul edilmez. Çoklu bağımlı "
                "istemler alternatif yolla gönderme yapmalıdır "
                "(örn. 'İstem 1 veya 2')."
            ),
            suggestion=(
                "'ila', 'ile', 've' yerine 'veya' kullanın "
                "(örn. 'İstem 3 veya 4'teki gibi')."
            ),
            excerpt=m.group(0),
            legal_basis="TürkPatent Patent Başvuru Kılavuzu — İstemler sf. 8-9",
        )
    ]


def check_claims_single_sentence(claims: str) -> list[Finding]:
    """Her istem tek cümle olmalı; sonda yalnızca bir nokta bulunmalı.

    Basit sezgisel: bir istem bloğunun içinde (sondaki nokta hariç) cümle
    sonu noktası varsa uyarı verilir. Kısaltmalar (No., vb.) yanlış pozitif
    üretebileceğinden bu kural WARNING seviyesindedir.
    """
    if not (claims or "").strip():
        return []
    # İstem bloklarını numara başlarına göre ayır
    starts = list(_CLAIM_START.finditer(claims))
    if not starts:
        return []
    findings: list[Finding] = []
    for i, m in enumerate(starts):
        begin = m.end()
        end = starts[i + 1].start() if i + 1 < len(starts) else len(claims)
        body = claims[begin:end].strip()
        # Sondaki noktayı at, geri kalanda cümle sonu noktası ara.
        core = body[:-1] if body.endswith(".") else body
        # ". " veya ".\n" → cümle sonu işareti. Sayı.sayı (3.14) hariç.
        inner = re.search(r"(?<!\d)\.(?:\s|$)", core)
        if inner:
            findings.append(
                Finding(
                    rule_id="claims.multi_sentence",
                    severity=Severity.WARNING,
                    section="istemler",
                    title=f"İstem {m.group(1)} birden fazla cümle içeriyor",
                    detail=(
                        "Her istem tek cümle olarak yazılmalı; nokta yalnızca "
                        "istemin sonunda bir kez kullanılmalıdır. Gerekli yerde "
                        "virgül veya noktalı virgül kullanın."
                    ),
                    suggestion=(
                        "İstem içindeki cümle sonu noktalarını ';' ile "
                        "değiştirip istemi tek cümleye dönüştürün."
                    ),
                    legal_basis="TürkPatent Patent Başvuru Kılavuzu sf. 11",
                )
            )
    return findings


# --------------------------------------------------------------------------
# ÖZET kuralları
# --------------------------------------------------------------------------

def check_abstract_has_title(abstract: str, invention_title: str) -> list[Finding]:
    """Özet başlığı altına buluş başlığı yazılmalı."""
    if not (invention_title or "").strip():
        return []  # başlık bilinmiyorsa kontrol edilemez
    norm_abs = _norm(abstract)
    norm_title = _norm(invention_title)
    if norm_title and norm_title in norm_abs:
        return []
    return [
        Finding(
            rule_id="abstract.missing_title",
            severity=Severity.ERROR,
            section="ozet",
            title="Özette buluş başlığı yok",
            detail=(
                "Özet sayfasının başına 'ÖZET' ifadesinin altına, başvuru "
                "formu ve tarifnamedeki ile aynı buluş başlığı yazılmalıdır."
            ),
            suggestion=(
                f"Özet metninin başına buluş başlığını ekleyin: "
                f"\"{invention_title.strip()}\""
            ),
            legal_basis="TürkPatent Patent Başvuru Kılavuzu — Özet sf. 14",
        )
    ]


def check_abstract_length(abstract: str) -> list[Finding]:
    """Özet, yönetmelik gereği ~150 kelimeyi aşmamalı."""
    words = len(re.findall(r"\S+", abstract or ""))
    if words <= 150:
        return []
    return [
        Finding(
            rule_id="abstract.too_long",
            severity=Severity.WARNING,
            section="ozet",
            title=f"Özet çok uzun ({words} kelime)",
            detail=(
                "Özet en fazla 150 kelime olmalıdır. Mevcut özet "
                f"{words} kelime içeriyor."
            ),
            suggestion=f"Özeti yaklaşık {words - 150} kelime kısaltın.",
            legal_basis="SMK Yönetmeliği — Özet",
        )
    ]


# --------------------------------------------------------------------------
# GENEL / başlık kuralları
# --------------------------------------------------------------------------

def check_section_headings(
    description: str, claims: str, abstract: str
) -> list[Finding]:
    """Başvuru unsurları doğru başlıklarla başlamalı."""
    findings: list[Finding] = []
    checks = [
        ("tarifname", description, "TARİFNAME"),
        ("istemler", claims, "İSTEMLER"),
        ("ozet", abstract, "ÖZET"),
    ]
    for section, text, expected in checks:
        if not (text or "").strip():
            continue
        head = _norm(text)[:120]
        if expected.lower() not in head:
            findings.append(
                Finding(
                    rule_id=f"{section}.missing_heading",
                    severity=Severity.WARNING,
                    section=section,
                    title=f"'{expected}' başlığı görünmüyor",
                    detail=(
                        f"{section.capitalize()} unsuru '{expected}' başlığıyla "
                        "başlamalıdır."
                    ),
                    suggestion=f"Metnin en başına '{expected}' başlığını ekleyin.",
                    legal_basis="TürkPatent Patent Başvuru Kılavuzu",
                )
            )
    return findings


def check_title_consistency(
    invention_title: str, description: str, abstract: str
) -> list[Finding]:
    """Buluş başlığı tarifname ve özette aynı olmalı."""
    if not (invention_title or "").strip():
        return []
    findings: list[Finding] = []
    norm_title = _norm(invention_title)
    for section, text in (("tarifname", description), ("ozet", abstract)):
        if not (text or "").strip():
            continue
        if norm_title not in _norm(text):
            findings.append(
                Finding(
                    rule_id=f"{section}.title_mismatch",
                    severity=Severity.INFO,
                    section=section,
                    title=f"Buluş başlığı {section} içinde bulunamadı",
                    detail=(
                        "Buluş başlığı; başvuru formu, tarifname ve özette "
                        "birebir aynı olmalıdır."
                    ),
                    suggestion=(
                        f"{section.capitalize()} içindeki buluş başlığını "
                        f"\"{invention_title.strip()}\" ile eşleştirin."
                    ),
                    legal_basis="TürkPatent Patent Başvuru Kılavuzu",
                )
            )
    return findings


# --------------------------------------------------------------------------
# Toplayıcı
# --------------------------------------------------------------------------

def run_deterministic_checks(
    *,
    description: str = "",
    claims: str = "",
    abstract: str = "",
    invention_title: str = "",
    jurisdiction: Jurisdiction = Jurisdiction.TR,
) -> list[Finding]:
    """Deterministik kuralları çalıştırır ve bulguları birleştirir.

    Ofise (jurisdiction) göre kural seti seçilir:
    - Ortak kurallar (istem numaralandırma, çoklu bağımlılık, tek cümle,
      özet uzunluğu) her ofiste geçerlidir.
    - TR-özel kurallar (Türkçe bölüm başlıkları, özette buluş başlığı,
      başlık tutarlılığı) yalnızca TürkPatent başvurularında çalışır.
    - EP/US-özel kurallar ilgili modüllerden gelir (epo.py, uspto.py).
    """
    findings: list[Finding] = []

    # --- Tüm ofislerde ortak kurallar ---
    # (istem yapısı kuralları; ofiseden bağımsız geçerli)
    findings += check_claims_subheadings(claims)
    findings += check_claims_numbering(claims)
    findings += check_claims_multi_dependency(claims)
    findings += check_claims_single_sentence(claims)

    # --- Ofise özgü kurallar ---
    # Özet uzunluğu her ofiste var ama EP/US kendi (farklı ciddiyet ve
    # dayanaklı) versiyonunu kullanır; çift bildirimi önlemek için ortak
    # check_abstract_length yalnızca TR'de çalışır.
    if jurisdiction == Jurisdiction.TR:
        findings += check_abstract_length(abstract)
        findings += check_abstract_has_title(abstract, invention_title)
        findings += check_section_headings(description, claims, abstract)
        findings += check_title_consistency(invention_title, description, abstract)
    elif jurisdiction == Jurisdiction.EP:
        from app.services.formality.epo import check_epo_rules

        findings += check_epo_rules(
            description=description, claims=claims, abstract=abstract
        )
    elif jurisdiction == Jurisdiction.US:
        from app.services.formality.uspto import check_uspto_rules

        findings += check_uspto_rules(
            description=description, claims=claims, abstract=abstract
        )

    return findings
