"""
doc_facts.py — Fact Lock Layer for Stepan v3.2.

Зберігає структуровані числові факти з документів у session (TTL=900s),
щоб уникнути інконсистентності RAG між запитами.

Ключові функції:
  extract_doc_facts(text)       → dict  (rule-based, без LLM)
  merge_doc_facts(old, new)     → dict  (merge з conflict detection)
  can_answer_from_facts(q, f)   → (bool, list[str])
  compute_scenario(q, facts)    → (bool, str)
"""

from __future__ import annotations

import re
import logging
from typing import Any

logger = logging.getLogger(__name__)

# ── Ключі фактів ────────────────────────────────────────────────────────────
# Усі числові значення в UAH або га.
FACT_KEYS = (
    "profit_uah",
    "revenue_uah",
    "cost_total_uah",
    "fertilizer_uah",
    "seed_uah",
    "area_ha",
    "profit_uah_per_ha",
    "cost_uah_per_ha",
)

# ── Словник тригерів у питаннях ──────────────────────────────────────────────
_QUESTION_TRIGGERS: dict[str, list[str]] = {
    "profit_uah":         ["прибут", "profit", "дохід", "заробіт", "чист"],
    "revenue_uah":        ["виручк", "revenue", "надходж"],
    "cost_total_uah":     ["витрат", "cost", "видатк", "загальн"],
    "fertilizer_uah":     ["добрив", "fertiliz", "мінерал"],
    "seed_uah":           ["насінн", "seed", "посів"],
    "area_ha":            ["площ", "гектар", " га", "area"],
    "profit_uah_per_ha":  ["грн/га", "на гектар", "per ha", "прибут.*га"],
    "cost_uah_per_ha":    ["витрат.*га", "cost.*ha", "на гектар.*витрат"],
}

# ── Регулярні вирази для числових значень ────────────────────────────────────
# Числа типу: 5 972 016, 9684737, 12 016.13, 497, 5\u00a0972\u00a0016 (NBSP)
_NUM = r"[\d][\d\s\u00a0\u202f]*(?:[.,][\d]+)?"

# Шаблони для витягування фактів (порядок важливий — специфічніші спочатку)
_PATTERNS: list[tuple[str, str]] = [
    # грн/га — спочатку, специфічніші паттерни
    (r"(?:прибут\w*)\s*[—:–]\s*(" + _NUM + r")\s*грн/га", "profit_uah_per_ha"),
    (r"(?:прибут\w*\s+(?:на\s+)?(?:гектар|га)[^.]{0,20}?)\s*[:\s]\s*(" + _NUM + r")\s*грн/га", "profit_uah_per_ha"),
    (r"(" + _NUM + r")\s*грн/га\s*(?:прибут)", "profit_uah_per_ha"),
    (r"прибут\w+\s+на\s+гектар[^.]{0,30}(" + _NUM + r")\s*грн/га", "profit_uah_per_ha"),
    (r"(?:витрат\w*)\s*[—:–\(]*\s*(" + _NUM + r")\s*грн/га", "cost_uah_per_ha"),
    (r"(" + _NUM + r")\s*грн/га", "cost_uah_per_ha"),
    # га / гектар
    (r"площ\w*\s*[—:–]\s*(" + _NUM + r")\s*(?:га|гектар)", "area_ha"),
    (r"(" + _NUM + r")\s*(?:га\b|гектар)", "area_ha"),
    # Конкретні категорії (грн / гривень)
    (r"добрив\w*[^.]*?(" + _NUM + r")\s*(?:грн|гривень)", "fertilizer_uah"),
    (r"насінн\w*[^.]*?(" + _NUM + r")\s*(?:грн|гривень)", "seed_uah"),
    (r"(?:загальн\w*\s*)?витрат\w*[^.]*?(" + _NUM + r")\s*(?:грн|гривень)", "cost_total_uah"),
    (r"виручк\w*[^.]*?(" + _NUM + r")\s*(?:грн|гривень)", "revenue_uah"),
    (r"прибут\w*[^.]*?(" + _NUM + r")\s*(?:грн|гривень)", "profit_uah"),
    # Зворотній порядок
    (r"(" + _NUM + r")\s*(?:грн|гривень)[^.]*?прибут", "profit_uah"),
    (r"(" + _NUM + r")\s*(?:грн|гривень)[^.]*?виручк", "revenue_uah"),
    (r"(" + _NUM + r")\s*(?:грн|гривень)[^.]*?добрив", "fertilizer_uah"),
    (r"(" + _NUM + r")\s*(?:грн|гривень)[^.]*?витрат", "cost_total_uah"),
]


def _parse_number(s: str) -> float:
    """
    Fix 3: Нормалізація числового рядка з XLSX/тексту.
    Обробляє: пробіли, NBSP, тонкі пробіли, кому як роздільник тисяч,
    одиниці виміру (грн, грн/га, га, ha, %), скобки.
    """
    s = str(s).strip()
    # Прибираємо одиниці виміру та стрічки після числа
    s = re.sub(r"\s*(грн/га|грн|гривень|ga|га\b|ha\b|%|тис\.?|млн\.?)\s*$", "", s, flags=re.IGNORECASE)
    # Прибираємо дужки (від'ємні числа в бухобліку: "(1 234)" → "-1234")
    negative = s.startswith("(") and s.endswith(")")
    if negative:
        s = s[1:-1].strip()
    # Прибираємо всі пробільні символи всередині числа:
    # звичайний пробіл, NBSP (U+00A0), тонкий пробіл (U+202F), нерозривний вузький пробіл
    s = re.sub(r"(?<=\d)[\s\u00a0\u202f\u2009\u2007]+(?=\d)", "", s)
    # Якщо кома — роздільник тисяч (5,972,016) → прибрати
    # Якщо кома — десяткова (1,5) → замінити на крапку
    comma_count = s.count(",")
    dot_count = s.count(".")
    if comma_count >= 2 or (comma_count == 1 and dot_count == 0 and len(s) - s.index(",") > 3):
        # "5,972,016" або "1,234,567" — роздільник тисяч
        s = s.replace(",", "")
    else:
        # "1,5" або "12,016.13" — десяткова кома
        s = s.replace(",", ".")
    # Прибираємо зайві крапки (залишаємо лише останню як десяткову)
    parts = s.split(".")
    if len(parts) > 2:
        s = "".join(parts[:-1]) + "." + parts[-1]
    s = s.strip()
    try:
        val = float(s)
        return -val if negative else val
    except ValueError:
        return 0.0


def extract_doc_facts(text: str) -> dict[str, float]:
    """
    Rule-based витягує числові факти з тексту.
    Повертає тільки впевнено розпізнані пари {key: float}.
    Fail-safe: будь-яка помилка → повертає {}.
    """
    if not text:
        return {}
    try:
        found: dict[str, float] = {}
        t = text.lower()
        for pattern, key in _PATTERNS:
            # Якщо ключ вже знайдений з конкретнішого паттерну — не перезаписувати
            if key in found:
                continue
            m = re.search(pattern, t, re.IGNORECASE)
            if m:
                val = _parse_number(m.group(1))
                if val > 0:
                    found[key] = val
        return found
    except Exception as exc:
        logger.debug("extract_doc_facts error (non-blocking): %s", exc)
        return {}


def merge_doc_facts(old: dict, new: dict) -> dict:
    """
    Зливає старий і новий словники фактів.
    - Якщо ключ новий — додає.
    - Якщо ключ є і значення відрізняється > 1% — фіксує конфлікт, не перезаписує.
    Fail-safe: помилка → повертає old.
    """
    if not new:
        return old
    try:
        merged = dict(old)
        conflicts: dict[str, dict] = merged.get("conflicts", {})

        for key, new_val in new.items():
            if key in ("conflicts", "needs_recheck"):
                continue
            old_val = merged.get(key)
            if old_val is None:
                merged[key] = new_val
            elif old_val > 0 and abs(new_val - old_val) / old_val > 0.01:
                conflicts[key] = {"old": old_val, "new": new_val}
                merged["needs_recheck"] = True
            # Якщо однаковий — залишаємо старий (стабільність)

        if conflicts:
            merged["conflicts"] = conflicts
        return merged
    except Exception as exc:
        logger.debug("merge_doc_facts error (non-blocking): %s", exc)
        return old


def can_answer_from_facts(question: str, facts: dict) -> tuple[bool, list[str]]:
    """
    Визначає чи питання стосується ключів що є у facts.
    Повертає (True, [keys]) якщо можна відповісти з кешу.
    """
    if not facts or not question:
        return False, []
    try:
        q = question.lower()
        matched: list[str] = []
        for key, triggers in _QUESTION_TRIGGERS.items():
            if key not in facts:
                continue
            for trigger in triggers:
                if re.search(trigger, q):
                    if key not in matched:
                        matched.append(key)
                    break
        return bool(matched), matched
    except Exception as exc:
        logger.debug("can_answer_from_facts error: %s", exc)
        return False, []


def compute_scenario(question: str, facts: dict) -> tuple[bool, str]:
    """
    Розраховує прості сценарії:
    - "якщо добрива ×2, яким буде прибуток?"
    Повертає (True, text) якщо розрахунок можливий, (False, "") інакше.
    """
    if not facts or not question:
        return False, ""
    try:
        q = question.lower()

        # Сценарій: добрива × N → новий прибуток
        double_fertilizer = re.search(
            r"добрив\w*.{0,30}(?:збільш|×\s*2|удвіч|вдві|2\s*раз|подвоїт)", q
        ) or re.search(
            r"(?:збільш|удвіч|вдві|2\s*раз).{0,30}добрив", q
        )

        if double_fertilizer:
            profit = facts.get("profit_uah")
            fertilizer = facts.get("fertilizer_uah")
            if profit and fertilizer:
                new_profit = profit - fertilizer   # добрива×2 → +fertilizer зайвих витрат
                delta = -fertilizer
                sign = "зменшиться" if delta < 0 else "збільшиться"
                abs_delta = abs(delta)
                area = facts.get("area_ha")
                per_ha = ""
                if area and area > 0:
                    per_ha = f" ({new_profit/area:,.0f} грн/га)".replace(",", " ")
                text = (
                    f"Якщо витрати на добрива збільшити вдвічі (+{fertilizer:,.0f} грн), "
                    f"прибуток {sign} на {abs_delta:,.0f} грн і складе "
                    f"{new_profit:,.0f} грн{per_ha}."
                ).replace(",", " ")
                return True, text
            missing = []
            if not profit:
                missing.append("прибуток")
            if not fertilizer:
                missing.append("витрати на добрива")
            return False, f"Для розрахунку потрібно: {', '.join(missing)}."

    except Exception as exc:
        logger.debug("compute_scenario error: %s", exc)

    return False, ""


# ── PROMPT 26: Self-Correction helpers ───────────────────────────────────────

_CLAIM_PATTERNS: list[tuple[re.Pattern, str, bool]] = [
    (re.compile(r"(нем[аі]\w*\s+прибут|прибут\w+\s+нем[аі]|без\s+прибут)", re.I), "profit_present", False),
    (re.compile(r"(є\s+прибут|прибут\w+\s+[—–:]\s*\d|прибут.*\d.*грн)", re.I), "profit_present", True),
    (re.compile(r"(нем[аі]\w*\s+витрат|витрат\w+\s+нем[аі])", re.I), "cost_present", False),
    (re.compile(r"(є\s+витрат|витрат\w+\s+[—–:]\s*\d)", re.I), "cost_present", True),
]

_CORRECTION_PHRASES: dict[tuple, str] = {
    ("profit_present", False, True): (
        "Раніше я написав, що прибутку в документі немає. Це було неточно — він є. "
    ),
    ("profit_present", True, False): (
        "Раніше я вказував прибуток. Схоже, у цьому фрагменті його не знайшов — перевір, будь ласка. "
    ),
    ("cost_present", False, True): (
        "Раніше я написав, що витрат немає. Це було неточно — вони є. "
    ),
}


def extract_fact_claims(text: str) -> list[dict]:
    """Витягує fact claims з тексту відповіді агента (для Self-Correction)."""
    import time as _time
    if not text:
        return []
    claims = []
    for pattern, key, value in _CLAIM_PATTERNS:
        if pattern.search(text):
            claims.append({"key": key, "value": value, "ts": _time.time()})
    return claims


def build_self_correction(
    response_text: str,
    facts: dict,
    session: dict,
    current_doc_id: str | None = None,
) -> str:
    """
    Якщо нова відповідь суперечить попереднім claims → повертає prefix-речення.
    Тільки для deep-mode. Self-correction спрацьовує лише в межах одного doc_id.
    Fail-safe: помилка → "".
    """
    try:
        # v3.3: Doc Anchor Guard — не виправляємо між різними документами
        if current_doc_id is not None:
            session_doc_id = session.get("active_doc_id")
            if session_doc_id and session_doc_id != current_doc_id:
                return ""

        prev_claims: list[dict] = session.get("fact_claims") or []
        if not prev_claims:
            return ""
        new_claims = extract_fact_claims(response_text)
        if not new_claims:
            return ""
        prev_by_key: dict[str, bool] = {c["key"]: c["value"] for c in prev_claims}
        for claim in new_claims:
            key = claim["key"]
            new_val = claim["value"]
            old_val = prev_by_key.get(key)
            if old_val is not None and old_val != new_val:
                phrase = _CORRECTION_PHRASES.get((key, old_val, new_val), "")
                if phrase:
                    return phrase
        return ""
    except Exception:
        return ""


def format_facts_as_text(facts: dict) -> str:
    """Форматує doc_facts у коротку людяну відповідь."""
    lines = []
    labels = {
        "profit_uah":        "Прибуток",
        "revenue_uah":       "Виручка",
        "cost_total_uah":    "Загальні витрати",
        "fertilizer_uah":    "Витрати на добрива",
        "seed_uah":          "Витрати на насіння",
        "area_ha":           "Площа",
        "profit_uah_per_ha": "Прибуток на га",
        "cost_uah_per_ha":   "Витрати на га",
    }
    units = {
        "area_ha": "га",
        "profit_uah_per_ha": "грн/га",
        "cost_uah_per_ha": "грн/га",
    }
    for key, label in labels.items():
        val = facts.get(key)
        if val is None:
            continue
        unit = units.get(key, "грн")
        if unit == "грн":
            lines.append(f"• {label}: {val:,.0f} {unit}".replace(",", " "))
        else:
            lines.append(f"• {label}: {val:,.2f} {unit}".replace(",", "."))
    return "\n".join(lines)
