#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
# See LICENSE file for details.
"""
Decision Store: JSONL-based decision storage with supersession and dedup.

Writes to Hippocampus/Decisions/auto_decisions.jsonl (new, clean).
The legacy auto_decisions.json (garbage data) is left untouched.

Dependencies: brain_utils, brain_io, brain_analysis, pulse_crypto
"""

import hashlib
import json
from datetime import datetime, timezone

from pulse.core.brain_utils import DECISIONS_DIR, DECISIONS_JSONL
from pulse.core.brain_io import _acquire
from pulse.core.brain_analysis import text_similarity


def store_decision(decision: dict, dry_run: bool = False) -> bool:
    """Validate, dedup, check supersession, and append decision to JSONL.

    Returns True if stored, False if skipped (dedup/invalid).
    """
    choice = decision.get("choice", "").strip()
    if not choice or len(choice) < 3:
        return False

    # Generate stable ID from choice text
    fp = hashlib.md5(choice[:80].lower().strip().encode("utf-8")).hexdigest()[:12]
    decision["id"] = f"dec_{fp}"

    # Sign the entry
    try:
        from pulse.core.pulse_crypto import sign_item
        source = decision.get("participants", ["unknown"])[0] if decision.get("participants") else "bridge"
        decision = sign_item(decision, source)
    except Exception:
        pass

    if dry_run:
        return True

    DECISIONS_DIR.mkdir(parents=True, exist_ok=True)

    with _acquire(DECISIONS_JSONL):
        # Dedup check: skip if same fingerprint already active
        existing = _load_decisions()
        for ex in existing:
            if ex.get("id") == decision["id"] and ex.get("status") == "active":
                return False

        # Supersession check: mark old decision as superseded if same scope
        scope = decision.get("scope", "general")
        superseded = find_superseded(choice, scope, existing)
        if superseded:
            decision["_supersedes"] = superseded.get("id", "")
            _mark_superseded(existing, superseded["id"], decision["id"])

        # Intent conflict check: flag if decision contradicts an active want
        intent_hit = check_intent_conflict(choice)
        if intent_hit:
            conflict_text = intent_hit.get("want") or intent_hit.get("title") or ""
            if conflict_text:
                decision["_intent_conflict"] = conflict_text

        # SQLite-first: insert to SQLite; only write JSONL if SQLite fails
        sqlite_ok = False
        try:
            from pulse.core.brain_db import insert_knowledge as _ins
            _ins("decisions", {
                "content": decision.get("choice", ""),
                "title": decision.get("choice", "")[:80],
                "type": "decision",
                "domain": decision.get("scope", "general"),
                "agent": (decision.get("participants") or ["unknown"])[0],
                "timestamp": decision.get("timestamp", ""),
                "salience": float(decision.get("salience", 1.5)),
                "confidence": float(decision.get("confidence", 0.7)),
                "consolidation_count": int(decision.get("consolidation_count", 1)),
                "evidence_count": 1,
                "validity": decision.get("status", "active"),
        "metadata": __import__("json").dumps({
            "reasoning": decision.get("reasoning", ""),
            "alternatives": decision.get("alternatives", []),
            "context": decision.get("context", ""),
            "supersedes": decision.get("_supersedes", ""),
        }),
                "id": decision.get("id", ""),
            })
            sqlite_ok = True
        except Exception:
            pass  # SQLite failure → fall through to JSONL

        # Append to JSONL — always update in-memory list for supersession tracking,
        # but only persist to disk if SQLite didn't take it
        existing.append(decision)
        if not sqlite_ok:
            _write_decisions(existing)

    return True


def find_superseded(choice: str, scope: str, existing: list = None) -> dict:
    """Find the most recent active decision that the new choice would supersede.

    Returns the old decision dict, or None.
    Requires text_similarity >= 0.55 AND same scope.
    """
    if existing is None:
        existing = _load_decisions()

    choice_lower = choice[:80].lower()
    candidates = []
    for dec in existing:
        if dec.get("status") != "active":
            continue
        if dec.get("scope", "general") != scope:
            continue
        old_choice = dec.get("choice", "")[:80].lower()
        sim = text_similarity(choice_lower, old_choice)
        if sim >= 0.55 and choice_lower != old_choice:
            candidates.append((sim, dec))

    if not candidates:
        return None
    # Return the most recent matching decision (not the oldest)
    candidates.sort(key=lambda x: x[1].get("timestamp", ""), reverse=True)
    return candidates[0][1]


def check_intent_conflict(choice: str) -> dict:
    """Cross-reference choice against active wants/intents.

    Returns the conflicting want dict if similarity >= 0.4, or None.
    """
    try:
        from pulse.core.brain_utils import INTENTS_DIR

        choice_lower = choice[:100].lower()
        best_sim, best_match = 0.0, None

        # Check wants from SQLite (JSON Kill List migration)
        try:
            from pulse.core.brain_db import get_wants
            for w in get_wants():
                want_text = w.get("want", "")
                if not want_text:
                    continue
                sim = text_similarity(choice_lower, want_text[:100].lower())
                if sim > best_sim:
                    best_sim, best_match = sim, w
        except Exception:
            pass

        # Check strategic intents from SQLite (JSON Kill List migration)
        try:
            from pulse.core.brain_db import get_strategic_intents
            for intent in get_strategic_intents():
                if intent.get("status") not in (None, "pending", "active"):
                    continue
                intent_text = intent.get("title", "")
                if not intent_text:
                    continue
                sim = text_similarity(choice_lower, intent_text[:100].lower())
                if sim > best_sim:
                    best_sim, best_match = sim, intent
        except Exception:
            pass

        if best_sim >= 0.4 and best_match:
            return best_match
    except Exception:
        pass
    return None


def check_anti_pattern_conflict(choice: str) -> dict:
    """Cross-reference choice against active anti-patterns.

    Returns the conflicting anti-pattern dict, or None.
    """
    try:
        from pulse.core.brain_db import get_active_anti_patterns
        ap_list = get_active_anti_patterns()

        choice_lower = choice[:100].lower()
        for ap in ap_list:
            if ap.get("status") != "active":
                continue
            rule_text = (ap.get("rule", "") + " " + ap.get("description", "")).lower()
            sim = text_similarity(choice_lower, rule_text[:100])
            if sim >= 0.35:
                return ap
    except Exception:
        pass
    return None


def _load_decisions() -> list:
    """Load all decisions from JSONL."""
    if not DECISIONS_JSONL.exists():
        return []
    entries = []
    try:
        for line in DECISIONS_JSONL.read_text(encoding="utf-8").splitlines():
            line = line.strip()
            if line:
                try:
                    entries.append(json.loads(line))
                except json.JSONDecodeError:
                    continue
    except OSError:
        pass
    return entries


def _write_decisions(entries: list) -> None:
    """Write all decisions back to JSONL (atomic rewrite)."""
    DECISIONS_JSONL.parent.mkdir(parents=True, exist_ok=True)
    lines = [json.dumps(e, ensure_ascii=False) for e in entries]
    DECISIONS_JSONL.write_text("\n".join(lines) + "\n", encoding="utf-8")


def _mark_superseded(entries: list, old_id: str, new_id: str) -> None:
    """Mark an existing decision as superseded (in-memory, caller writes)."""
    for e in entries:
        if e.get("id") == old_id:
            e["status"] = "superseded"
            e["superseded_by"] = new_id
            e["superseded_at"] = datetime.now(timezone.utc).isoformat()
            break
