#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Causal Pathweaver — Brain V8 Phase 6 (D6)

Mines golden paths (proven success strategies) from completed tasks
and procedural sequences. CRDT-deduplicates competing agent approaches,
scores by evidence weight, and saves to state/golden_paths.json for
boot injection.

Mining helpers in pathweaver_mining.py (split for Law 7).
Scheduled by orchestrator every 12h. Also runnable: --once --verbose
"""
from __future__ import annotations

import logging
import os
import sys
from datetime import datetime, timezone, timedelta
from pathlib import Path

if os.name == "nt" and __name__ == "__main__":
    sys.stdout.reconfigure(encoding="utf-8")
    sys.stderr.reconfigure(encoding="utf-8")

ROOT_DIR = Path(__file__).resolve().parents[4]
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))

from pulse.core.brain_utils import (
    load_json_dict, save_json,
    STATE_DIR, now_iso as _now_iso,
)
from pulse.core.brain_io import _acquire
from pulse.daemons.synthesis.pathweaver.pathweaver_mining import (
    get_completed_tasks, _get_procedural_sequences,
    _group_by_domain, extract_domain_patterns,
    extract_procedural_paths, dedup_paths,
    get_session_patterns, MIN_OBSERVATIONS,
)

log = logging.getLogger("pulse.pathweaver")

GOLDEN_PATHS_FILE = STATE_DIR / "golden_paths.json"
DECAY_DAYS = 14


# --- Public API (3 functions) ---

def mine_golden_paths(verbose: bool = False) -> list[dict]:
    """Mine + deduplicate success sequences. Returns golden paths.

    1. Load completed tasks + procedural sequences
    2. Extract domain patterns from task outcomes
    3. Extract paths from procedural log
    4. CRDT-dedup competing approaches
    5. Save to state/golden_paths.json
    """
    # Source 1: Completed tasks -> domain patterns
    tasks = get_completed_tasks()
    grouped = _group_by_domain(tasks)
    domain_paths = []
    for domain, domain_tasks in grouped.items():
        if len(domain_tasks) >= MIN_OBSERVATIONS:
            pattern = extract_domain_patterns(domain, domain_tasks)
            if pattern:
                domain_paths.append(pattern)

    if verbose:
        log.info("[D6] %d domain patterns from %d completed tasks",
                 len(domain_paths), len(tasks))

    # Source 2: Procedural sequences -> proven chains
    sequences = _get_procedural_sequences()
    proc_paths = extract_procedural_paths(sequences)

    if verbose:
        log.info("[D6] %d procedural paths from %d sequences",
                 len(proc_paths), len(sequences))

    # Source 3: Interactive session learnings -> proven approaches
    session_paths = get_session_patterns()

    if verbose:
        log.info("[D6] %d session paths from interactive sessions",
                 len(session_paths))

    # Merge and dedup
    all_paths = domain_paths + proc_paths + session_paths
    golden = dedup_paths(all_paths)

    # Save with filelock
    with _acquire(GOLDEN_PATHS_FILE):
        save_json(GOLDEN_PATHS_FILE, {
            "paths": golden,
            "last_updated": _now_iso(),
            "sources": {
                "completed_tasks": len(tasks),
                "procedural_sequences": len(sequences),
                "interactive_sessions": len(session_paths),
            },
        })

    if verbose:
        log.info("[D6] Saved %d golden paths", len(golden))
        for p in golden[:5]:
            log.info("  [%.0f%%] %s (%d obs)",
                     p["confidence"] * 100, p["title"], p["observation_count"])

    return golden


def get_golden_paths(domain: str = "", max_paths: int = 3) -> list[dict]:
    """Return top paths for a domain. Used by boot loaders.

    If domain is empty, returns top paths across all domains.
    """
    if not GOLDEN_PATHS_FILE.exists():
        return []
    try:
        data = load_json_dict(GOLDEN_PATHS_FILE)
        paths = [p for p in data.get("paths", [])
                 if p.get("status") == "active"]
        if domain:
            exact = [p for p in paths if p.get("domain") == domain]
            if exact:
                paths = exact
            else:
                paths = [p for p in paths
                         if domain.lower() in p.get("domain", "").lower()]
        paths.sort(key=lambda p: -p.get("confidence", 0))
        return paths[:max_paths]
    except Exception:
        return []


def decay_stale_paths(max_age_days: int = DECAY_DAYS) -> int:
    """Expire paths older than threshold. Returns count of expired paths.

    Called by orchestrator scheduler with --decay flag.
    """
    if not GOLDEN_PATHS_FILE.exists():
        return 0
    try:
        with _acquire(GOLDEN_PATHS_FILE):
            data = load_json_dict(GOLDEN_PATHS_FILE)
            paths = data.get("paths", [])
            cutoff = datetime.now(timezone.utc) - timedelta(days=max_age_days)
            expired = 0

            for p in paths:
                if p.get("status") != "active":
                    continue
                try:
                    ts = datetime.fromisoformat(p.get("timestamp", ""))
                    if ts.tzinfo is None:
                        ts = ts.replace(tzinfo=timezone.utc)
                    if ts < cutoff:
                        p["status"] = "expired"
                        expired += 1
                except (ValueError, TypeError):
                    pass

            paths = [p for p in paths if p.get("status") == "active"]
            data["paths"] = paths
            data["last_updated"] = _now_iso()
            save_json(GOLDEN_PATHS_FILE, data)

        if expired:
            log.info("[D6] Decayed %d stale paths", expired)
        return expired
    except Exception as e:
        log.error("[D6] Decay error: %s", e)
        return 0


# --- CLI Entry Point ---

if __name__ == "__main__":
    logging.basicConfig(
        level=logging.INFO,
        format="%(asctime)s [D6] %(message)s",
        datefmt="%H:%M:%S",
    )
    if "--once" in sys.argv:
        paths = mine_golden_paths(verbose=True)
        print(f"\n[D6] Mined {len(paths)} golden path(s)")
        for p in paths:
            print(f"  [{p['domain']}] {p['title']} "
                  f"(conf={p['confidence']}, obs={p['observation_count']})")
            for i, step in enumerate(p.get("steps", []), 1):
                print(f"    {i}. {step}")
    elif "--decay" in sys.argv:
        count = decay_stale_paths()
        print(f"[D6] Decayed {count} stale paths")
    elif "--status" in sys.argv:
        paths = get_golden_paths(max_paths=20)
        print(f"Active golden paths: {len(paths)}")
        for p in paths:
            print(f"  [{p['domain']}] {p['title']} "
                  f"(conf={p['confidence']}, obs={p['observation_count']})")
    else:
        print("[D6] Causal Pathweaver")
        print("  --once    Mine golden paths now")
        print("  --decay   Expire stale paths")
        print("  --status  Show active paths")
