"""Phase A — TDM gate + text resolution + section split + dedup.

Covers (in the original `_run_compliance_check`):
  - TDM-reservation pre-check (§ 44b UrhG)
  - Step 1   Resolve texts (URL fetch / pasted text / auto-reclassify)
  - Step 1a  Auto-discovery of missing canonical doc_types
  - Step 1b  Section splitting (shared URL → multiple doc_types,
             DSI → Cookie/Social-Media auto-fill)
  - Step 1c  Cross-document keyword search
  - P15      Dedup of doc_types referencing the same source document

Returns True to continue, False if the run was aborted (TDM denied).
"""

from __future__ import annotations

import logging

from ._constants import _compliance_check_jobs
from ._discovery import _autodiscover_missing
from ._fetch import _fetch_text
from ._helpers import _update

logger = logging.getLogger(__name__)


async def run_phase_a(state: dict) -> bool:
    """Run TDM gate + Step 1 + Step 1a-c + P15 dedup. Mutate state in place."""
    check_id = state["check_id"]
    req = state["req"]

    # Reset anchor-locator cache per run (avoid cross-run leak)
    try:
        from compliance.services.doc_anchor_locator import reset_cache
        reset_cache()
    except Exception:
        pass

    # P7: TDM-Reservation-Check der Base-Domain (§ 44b UrhG).
    # Bei reserved/denied: Run sofort beenden, kein Crawl.
    try:
        from compliance.services.tdm_reservation_check import (
            check_tdm_reservation, is_crawl_allowed,
        )
        first_url = next(
            (d.url for d in req.documents if d.url), "",
        )
        if first_url:
            tdm = await check_tdm_reservation(first_url)
            _compliance_check_jobs[check_id]["tdm"] = tdm
            # P12: Bei tdm_override + Reason wird NICHT abgebrochen,
            # sondern nur dokumentiert. Override ohne Reason wird ignoriert.
            override_active = (
                req.tdm_override
                and len((req.tdm_override_reason or "").strip()) >= 10
            )
            if not is_crawl_allowed(tdm) and not override_active:
                _compliance_check_jobs[check_id]["status"] = "skipped_tdm"
                _compliance_check_jobs[check_id]["error"] = (
                    f"TDM-Vorbehalt fuer {tdm.get('domain')} erkannt "
                    f"(status={tdm.get('status')}) — Crawl nach § 44b "
                    f"UrhG nicht zulaessig. Signals: "
                    f"{[s.get('src') for s in tdm.get('signals', [])]}"
                )
                _compliance_check_jobs[check_id]["progress_pct"] = 100
                logger.info("TDM-skip check_id=%s domain=%s status=%s",
                            check_id, tdm.get("domain"), tdm.get("status"))
                return False
            if override_active and not is_crawl_allowed(tdm):
                _compliance_check_jobs[check_id]["tdm_override"] = {
                    "reason": req.tdm_override_reason.strip()[:500],
                    "original_status": tdm.get("status"),
                }
                logger.warning(
                    "TDM-Override aktiv: check_id=%s domain=%s "
                    "status=%s reason=%r",
                    check_id, tdm.get("domain"), tdm.get("status"),
                    req.tdm_override_reason.strip()[:80],
                )
    except Exception as e:
        logger.warning("TDM-check failed (proceeding): %s", e)

    # Step 1: Resolve texts (fetch from URL if needed) — 0-30%
    _update(check_id, "Texte werden geladen...", 1)
    doc_texts: dict[str, str] = {}
    doc_entries: list[dict] = []

    # Cache fetched URLs to detect duplicates
    url_text_cache: dict[str, str] = {}

    n_docs = max(1, len(req.documents))
    # User-pasted-Tabellen-Vendors (kein LLM noetig) — werden weiter
    # unten in cmp_vendors gemerged.
    pasted_table_vendors: list[dict] = []
    for i, doc in enumerate(req.documents):
        pct = int(1 + (i / n_docs) * 29)
        _update(check_id, f"Texte laden {i+1}/{n_docs}: {doc.doc_type}...", pct)
        text = (doc.text or "").strip()
        input_source = "url"
        cmp_payloads: list[dict] = []
        if text:
            input_source = "text"
            if doc.url:
                input_source = "text+url"  # User hat beide gefuellt
                logger.info(
                    "doc_type=%s: User hat URL UND Text geliefert — "
                    "Text gewinnt, URL wird als Quellen-Referenz behalten",
                    doc.doc_type,
                )
        elif doc.url:
            url_key = doc.url.strip().rstrip("/").lower()
            if url_key in url_text_cache:
                text = url_text_cache[url_key]
            else:
                text, cmp_payloads = await _fetch_text(doc.url, doc_type=doc.doc_type)
                if text:
                    url_text_cache[url_key] = text

        # Auto-Reclassify-Check: wenn der user Text in das falsche
        # Doc-Type-Feld kopiert hat (z.B. Impressum-Text in DSE),
        # erkennen und ggf. umtaggen.
        actual_doc_type = doc.doc_type
        reclassify_hint: dict | None = None
        if input_source.startswith("text") and len(text) >= 500:
            try:
                from compliance.services.doc_type_classifier import (
                    detect_mismatch,
                )
                reclassify_hint = detect_mismatch(doc.doc_type, text)
                if reclassify_hint and reclassify_hint["action"] == "reclassify":
                    actual_doc_type = reclassify_hint["detected"]
                    logger.info(
                        "doc_type AUTO-RECLASSIFY: deklariert=%s "
                        "erkannt=%s (score %d vs %d) — uebernehme erkannten Typ",
                        doc.doc_type, actual_doc_type,
                        reclassify_hint["detected_score"],
                        reclassify_hint["declared_score"],
                    )
            except Exception as e:
                logger.warning("doc_type_classifier failed: %s", e)

        # Cookie-Tabelle: wenn User Tabelle reinkopiert hat, deterministisch
        # parsen (kein LLM noetig) und Vendors gleich ableiten.
        if input_source.startswith("text") and actual_doc_type == "cookie":
            try:
                from compliance.services.cookies_table_parser import (
                    parse_cookie_table,
                )
                tab_vendors = parse_cookie_table(text)
                if tab_vendors:
                    pasted_table_vendors.extend(tab_vendors)
                    logger.info(
                        "Cookie-Tabelle erkannt im pasted Text — "
                        "%d Vendors / %d Cookies deterministisch geparst",
                        len(tab_vendors),
                        sum(len(v.get("cookies", [])) for v in tab_vendors),
                    )
            except Exception as e:
                logger.warning("cookies_table_parser failed: %s", e)

        if text:
            doc_texts[actual_doc_type] = text
        doc_entries.append({
            "doc_type":         actual_doc_type,
            "declared_doc_type": doc.doc_type,
            "url":              doc.url,
            "text":             text,
            "word_count":       len(text.split()) if text else 0,
            "auto_discovered":  False,
            "discovery_attempted": False,
            "cmp_payloads":     cmp_payloads,
            "input_source":     input_source,
            "reclassify_hint":  reclassify_hint,
        })

    # Step 1a-bis: AUTO-DISCOVERY
    await _autodiscover_missing(
        check_id, doc_entries, doc_texts, url_text_cache,
    )

    # Step 1b: Section splitting — two cases:
    # 1. Same URL used for multiple doc_types → split by heading
    # 2. DSI text contains Cookie/Social-Media sections → auto-fill empty rows
    from compliance.services.section_splitter import (
        split_shared_texts, auto_fill_from_dsi, cross_search_documents,
    )
    split_shared_texts(doc_entries, url_text_cache)
    auto_fill_from_dsi(doc_entries)

    # Step 1c: Cross-document search — find doc_types in wrong documents (30-35%)
    _update(check_id, "Dokumente werden uebergreifend durchsucht...", 32)
    placement_findings = cross_search_documents(doc_entries)

    # Refresh doc_texts after all splitting/searching
    for entry in doc_entries:
        if entry.get("text"):
            doc_texts[entry["doc_type"]] = entry["text"]

    # P15: Dedupe — wenn mehrere Doc-Types DASSELBE Dokument referenzieren
    # (z.B. Safetykon: User gibt /datenschutz fuer dse + cookie + widerruf),
    # behalten wir nur den primaeren Doc-Type. Andere: leeren + note.
    # Priorität: dse > impressum > cookie > widerruf > agb > nutzungsbedingungen
    _DOC_PRIORITY = ["dse", "impressum", "cookie", "widerruf", "agb",
                     "nutzungsbedingungen", "social_media", "dsb"]
    seen_text_hash: dict[int, str] = {}
    for dt in _DOC_PRIORITY:
        entry = next((e for e in doc_entries if e.get("doc_type") == dt
                      and e.get("text")), None)
        if not entry:
            continue
        text_hash = hash((entry.get("text") or "").strip()[:1000])
        if text_hash in seen_text_hash:
            primary = seen_text_hash[text_hash]
            logger.info(
                "P15 dedup: doc_type=%s referenziert dasselbe Dokument "
                "wie %s (URL=%s) -> als Duplikat markiert.",
                dt, primary, entry.get("url", "")[:60],
            )
            entry["text"] = ""
            entry["word_count"] = 0
            entry["url"] = ""
            entry["dup_of"] = primary
            doc_texts.pop(dt, None)
        else:
            seen_text_hash[text_hash] = dt

    state["doc_texts"] = doc_texts
    state["doc_entries"] = doc_entries
    state["url_text_cache"] = url_text_cache
    state["pasted_table_vendors"] = pasted_table_vendors
    state["placement_findings"] = placement_findings
    return True
