import logging
#!/usr/bin/env python3
# Copyright (c) 2026 PULSE Contributors. Proprietary. All rights reserved.
"""
Consolidation Mining: Sequence extraction and cross-session pattern discovery.

Extracted from consolidation_helpers.py per Law 7 (files <= 300 lines).
Dependencies: Python stdlib + brain_utils (internal)
"""
import re
import sys
from collections import Counter
from datetime import datetime, timezone, timedelta
from pathlib import Path


from pulse.core.brain_utils import (
    HIPPOCAMPUS_DIR, PATTERNS_DIR, LESSONS_DIR,
    load_json, text_similarity, load_json_dict, save_json,
    now_iso as _now_iso,
)

SESSIONS_FILE = HIPPOCAMPUS_DIR / "Evidence" / "Sessions" / "agent_sessions.json"
PATTERNS_FILE = PATTERNS_DIR / "auto_patterns.md"
LESSONS_FILE = LESSONS_DIR / "auto_lessons.md"


def extract_event_sequences(days=7, verbose=False):
    """Find recurring event sequences from agent sessions over past N days.

    Extracts bigrams of (domain, outcome) pairs from agent sessions.
    Sequences appearing 3+ times are considered procedural patterns.
    """
    cutoff = datetime.now(timezone.utc) - timedelta(days=days)
    sessions = load_json(SESSIONS_FILE)
    if not isinstance(sessions, list):
        return []

    recent = []
    for s in sessions:
        ts = s.get("timestamp", "")
        if ts:
            try:
                t = datetime.fromisoformat(ts.replace("Z", "+00:00"))
                if t >= cutoff:
                    recent.append(s)
            except (ValueError, TypeError):
                recent.append(s)

    if verbose:
        print(f"  [MINING] {len(recent)} sessions in last {days} days")

    event_tags = []
    for s in recent:
        agent = s.get("agent", "unknown")
        has_failures = bool(s.get("failures"))
        files = s.get("files_touched", [])
        domain = "general"
        for f in files:
            fl = f.lower()
            if "brain" in fl or "hippocampus" in fl:
                domain = "brain"; break
            elif "daemon" in fl or "pulse" in fl:
                domain = "infrastructure"; break
            elif "test" in fl:
                domain = "testing"; break
        event_tags.append(f"{agent}:{domain}:{'fail' if has_failures else 'ok'}")

    # Track n-grams for chain lengths 2-5 (U10: Dr. STDP)
    all_ngrams = Counter()
    for n in range(2, 6):
        for i in range(len(event_tags) - n + 1):
            chain = " -> ".join(event_tags[i:i + n])
            all_ngrams[chain] += 1

    sequences = []
    for seq, count in all_ngrams.most_common(30):
        if count >= 3:
            steps = seq.count(" -> ") + 1
            ok_steps = seq.count(":ok")
            success_rate = round(ok_steps / steps, 2) if steps > 0 else 0.0
            sequences.append({
                "sequence": seq, "count": count, "steps": steps,
                "success_rate": success_rate,
                "confidence": min(0.95, 0.5 + count * 0.05),
            })
    # Promote 3+ step chains with 5+ occurrences to procedural_log
    proc_log = HIPPOCAMPUS_DIR / "Evidence" / "Metrics" / "procedural_log.json"
    try:
        proc_data = load_json(proc_log)
        if not isinstance(proc_data, list):
            proc_data = []
        existing = {p.get("sequence", "") for p in proc_data}
        new_procs = 0
        for seq in sequences:
            if seq["steps"] >= 3 and seq["count"] >= 5 and seq["sequence"] not in existing:
                proc_data.append({
                    "sequence": seq["sequence"], "count": seq["count"],
                    "steps": seq["steps"], "success_rate": seq["success_rate"],
                    "promoted_at": _now_iso(),
                })
                new_procs += 1
        if new_procs > 0:
            proc_log.parent.mkdir(parents=True, exist_ok=True)
            save_json(proc_log, proc_data)
    except Exception:
        logging.debug("Suppressed exception in consolidation_mining", exc_info=True)
    if verbose:
        print(f"  [MINING] Found {len(sequences)} recurring sequences (2-5 step chains)")
    return sequences


def mine_cross_session_patterns(days=7, verbose=False):
    """Find patterns that appear across multiple sessions.

    Scans auto_patterns.md and auto_lessons.md for similar entries
    from different dates, indicating cross-session reinforcement.
    """
    patterns_found = []
    for md_file in [PATTERNS_FILE, LESSONS_FILE]:
        if not md_file.exists():
            continue
        text = md_file.read_text(encoding="utf-8", errors="replace")
        sections = text.split("\n## ")
        entries = []
        for section in sections[1:]:
            lines = section.strip().split("\n")
            title = lines[0].strip()
            body = "\n".join(lines[1:]).strip()
            date_match = re.search(r"\*\*Date:\*\*\s*(\d{4}-\d{2}-\d{2})", body)
            conf_match = re.search(r"\*\*Confidence:\*\*\s*([\d.]+)", body)
            consol_match = re.search(r"consolidation_count.*?(\d+)", body)
            entries.append({
                "title": title, "body": body[:200],
                "date": date_match.group(1) if date_match else "",
                "confidence": float(conf_match.group(1)) if conf_match else 0.5,
                "consolidation_count": int(consol_match.group(1)) if consol_match else 1,
                "source": md_file.stem,
            })
        for i, e1 in enumerate(entries):
            similar_count = 0
            dates = {e1["date"]}
            max_conf = e1["confidence"]
            total_consol = e1["consolidation_count"]
            for j, e2 in enumerate(entries):
                if i == j:
                    continue
                if text_similarity(e1["title"], e2["title"]) >= 0.6:
                    similar_count += 1
                    dates.add(e2["date"])
                    max_conf = max(max_conf, e2["confidence"])
                    total_consol += e2["consolidation_count"]
            if similar_count >= 2 and len(dates) >= 2 and max_conf >= 0.6:
                patterns_found.append({
                    "title": e1["title"][:80], "occurrences": similar_count + 1,
                    "unique_dates": len(dates), "max_confidence": max_conf,
                    "total_consolidations": total_consol, "source": e1["source"],
                })
    seen = set()
    unique = []
    for p in patterns_found:
        key = p["title"][:40].lower()
        if key not in seen:
            seen.add(key)
            unique.append(p)
    if verbose:
        print(f"  [MINING] Found {len(unique)} cross-session patterns")
    return unique


def register_discovered_procedures(sequences, verbose=False):
    """Register discovered event sequences with procedural_tracker."""
    if not sequences:
        return 0
    try:
        from pulse.admin.procedural_tracker import record_execution
    except ImportError:
        if verbose:
            print("  [PROCEDURES] procedural_tracker not available")
        return 0
    registered = 0
    for seq in sequences:
        seq_name = seq.get("sequence", "")
        count = seq.get("count", 0)
        if not seq_name or count < 3:
            continue
        proc_id = re.sub(r"[^a-z0-9_]", "_", seq_name.lower().replace(" -> ", "_to_"))
        proc_id = re.sub(r"_+", "_", proc_id).strip("_")
        record_execution(proc_id, success=True)
        registered += 1
        if verbose:
            print(f"  [PROCEDURES] Registered: {proc_id} (count={count})")
    return registered


def _load_schemas():
    """Load schemas.json data and existing name set."""
    schemas_file = PATTERNS_DIR / "schemas.json"
    data = load_json_dict(schemas_file)
    if "schemas" not in data:
        data["schemas"] = []
    names = {s.get("name", "").lower() for s in data["schemas"]}
    return schemas_file, data, names


def _upsert_schema(schemas_data, existing_names, name, confidence, evidence,
                   extra_fields=None, verbose=False, label="SCHEMA"):
    """Insert or update a schema entry. Returns True if new."""
    if name.lower() in existing_names:
        for s in schemas_data["schemas"]:
            if s.get("name", "").lower() == name.lower():
                s["evidence_count"] = max(s.get("evidence_count", 0), evidence)
                s["confidence"] = max(s.get("confidence", 0), confidence)
                s["last_reinforced"] = _now_iso()
                break
        return False
    schema_id = f"S-{len(schemas_data['schemas']) + 1:03d}"
    schema = {"schema_id": schema_id, "name": name,
              "evidence_count": evidence, "confidence": round(confidence, 3),
              "created": _now_iso()}
    if extra_fields:
        schema.update(extra_fields)
    schemas_data["schemas"].append(schema)
    existing_names.add(name.lower())
    if verbose:
        print(f"  [{label}] Promoted: {name} (conf={confidence:.2f})")
    return True


def promote_cross_patterns_to_schemas(cross_patterns, verbose=False):
    """Promote high-confidence cross-session patterns to schemas.json."""
    schemas_file, schemas_data, existing_names = _load_schemas()
    new_count = 0
    for pattern in cross_patterns:
        confidence = pattern.get("max_confidence", 0)
        consolidations = pattern.get("total_consolidations", 0)
        unique_dates = pattern.get("unique_dates", 0)
        if confidence < 0.5 or (consolidations < 3 and unique_dates < 2):
            continue
        name = pattern["title"][:60]
        extra = {"description": pattern["title"],
                 "source": pattern.get("source", "consolidation"),
                 "unique_dates": unique_dates,
                 "total_consolidations": consolidations}
        if _upsert_schema(schemas_data, existing_names, name, confidence,
                          pattern.get("occurrences", 1), extra, verbose):
            new_count += 1
    if new_count > 0 or any(p.get("max_confidence", 0) >= 0.7 for p in cross_patterns):
        schemas_data["last_updated"] = _now_iso()
        save_json(schemas_file, schemas_data)
    if verbose:
        print(f"  [SCHEMA] {new_count} new schemas, {len(schemas_data['schemas'])} total")
    return {"status": "ok", "new_schemas": new_count,
            "total_schemas": len(schemas_data["schemas"])}


def promote_sequences_to_schemas(sequences, verbose=False):
    """Promote event sequences with 5+ occurrences to schemas.json."""
    schemas_file, schemas_data, existing_names = _load_schemas()
    new_count = 0
    for seq in sequences:
        count = seq.get("count", 0)
        if count < 5:
            continue
        confidence = seq.get("confidence", 0.5)
        name = f"Sequence: {seq['sequence'][:50]}"
        extra = {"description": f"Recurring event sequence: {seq['sequence']}",
                 "source": "sequence_mining", "type": "procedural"}
        if _upsert_schema(schemas_data, existing_names, name, confidence,
                          count, extra, verbose, "SCHEMA"):
            new_count += 1
    if new_count > 0:
        schemas_data["last_updated"] = _now_iso()
        save_json(schemas_file, schemas_data)
    if verbose:
        print(f"  [SCHEMA] {new_count} sequences promoted to schemas")
    return {"new_from_sequences": new_count}