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

Extraction and deduplication logic for golden paths.
Split from pathweaver.py per Law 7 (files <= 300 lines).
"""
from __future__ import annotations

import hashlib
from collections import defaultdict

from pulse.core.brain_utils import (
    load_json, load_json_dict,
    HIPPOCAMPUS_DIR, TASK_BOARD_FILE,
    text_similarity, now_iso as _now_iso,
)
from pulse.core.pulse_crdt import EvidenceCRDT

PROC_LOG = HIPPOCAMPUS_DIR / "Evidence" / "Metrics" / "procedural_log.json"
SESSIONS_FILE = HIPPOCAMPUS_DIR / "Evidence" / "Sessions" / "agent_sessions.json"
MAX_PATHS = 20
SIMILARITY_THRESHOLD = 0.7
MIN_OBSERVATIONS = 3

_DOMAIN_MAP = {
    "brain": ("brain/", "hippocampus/", "brain_"),
    "infrastructure": ("daemon/", "daemons/", "orchestrat"),
    "testing": ("test", "spec"),
    "backend": ("api/",),
    "cli": ("cli/",),
    "swarm": ("worker/",),
}


def _path_id(domain: str, title: str) -> str:
    """Stable ID from domain + title."""
    raw = f"{domain.lower()}|{title[:60].strip().lower()}"
    return f"gp_{hashlib.md5(raw.encode()).hexdigest()[:12]}"


def get_completed_tasks() -> list[dict]:
    """Get completed tasks grouped by domain with agent info."""
    board = load_json_dict(TASK_BOARD_FILE)
    tasks = []
    for d in board.get("dispatches", []):
        for t in d.get("tasks", []):
            if t.get("status") == "done" and t.get("outcome"):
                tasks.append({
                    "title": t.get("title", "")[:100],
                    "domain": t.get("domain", "general"),
                    "agent": t.get("completed_by", t.get("claimed_by", "")),
                    "outcome": t.get("outcome", "")[:200],
                    "complexity": t.get("complexity", "low"),
                })
    return tasks


def _get_procedural_sequences() -> list[dict]:
    """Load procedural log sequences with sufficient evidence."""
    data = load_json(PROC_LOG)
    if not isinstance(data, list):
        if isinstance(data, dict):
            data = data.get("procedures", [])
        else:
            return []
    return [p for p in data
            if p.get("executions", p.get("count", 0)) >= MIN_OBSERVATIONS
            and p.get("success_rate", 0) > 0.5]


def _group_by_domain(tasks: list[dict]) -> dict[str, list[dict]]:
    """Group tasks by domain."""
    groups: dict[str, list[dict]] = defaultdict(list)
    for t in tasks:
        groups[t.get("domain", "general")].append(t)
    return dict(groups)


def extract_domain_patterns(domain: str, tasks: list[dict]) -> dict:
    """Extract success pattern for a domain from completed tasks."""
    if not tasks:
        return {}

    total = len(tasks)
    agents = list({t.get("agent", "") for t in tasks if t.get("agent")})

    # Build descriptive steps based on domain patterns
    if domain in ("brain", "knowledge"):
        steps = [
            "Query brain for existing knowledge before changes",
            "Validate against anti-patterns",
            "Make targeted changes (under 300 lines)",
            "Run smoke tests to verify",
            "Save learnings on completion",
        ]
    elif domain in ("backend", "infrastructure", "refactoring"):
        steps = [
            "Read all target files before modifying",
            "Run smoke tests to establish baseline",
            "Make changes in small batches (under 250 lines)",
            "Run smoke tests after each batch",
            "Save learnings on completion",
        ]
    elif domain in ("testing", "tests"):
        steps = [
            "Identify test coverage gaps",
            "Write tests for edge cases first",
            "Run full test suite to verify no regressions",
            "Save learnings on completion",
        ]
    else:
        steps = [
            "Read relevant files and brain context",
            "Plan changes before implementing",
            "Make focused changes (one concern at a time)",
            "Verify changes work before completing",
            "Save learnings on completion",
        ]

    success_rate = round(total / max(total, 1), 2)
    confidence = round(success_rate * min(total / 10, 1.0), 2)

    return {
        "id": _path_id(domain, f"{domain} success pattern"),
        "domain": domain,
        "title": f"{domain.title()} Success Pattern",
        "steps": steps,
        "success_rate": success_rate,
        "observation_count": total,
        "confidence": confidence,
        "contributing_agents": agents[:5],
        "timestamp": _now_iso(),
        "status": "active",
    }


def extract_procedural_paths(sequences: list[dict]) -> list[dict]:
    """Convert procedural log sequences into golden paths."""
    paths = []
    for seq in sequences:
        raw_seq = seq.get("sequence", "")
        if not raw_seq:
            continue

        chain_parts = [p.strip() for p in raw_seq.split(" -> ")]
        if len(chain_parts) < 2:
            continue

        first_parts = chain_parts[0].split(":")
        domain = first_parts[1] if len(first_parts) > 1 else "general"

        steps = []
        for part in chain_parts:
            parts = part.split(":")
            agent = parts[0] if len(parts) > 0 else "unknown"
            d = parts[1] if len(parts) > 1 else "general"
            outcome = parts[2] if len(parts) > 2 else "ok"
            steps.append(f"{agent} executes {d} task -> {outcome}")

        count = seq.get("executions", seq.get("count", 0))
        sr = seq.get("success_rate", 0.5)
        confidence = round(sr * min(count / 10, 1.0), 2)

        paths.append({
            "id": _path_id(domain, raw_seq[:60]),
            "domain": domain,
            "title": f"Proven {domain.title()} Sequence ({len(chain_parts)} steps)",
            "steps": steps[:5],
            "success_rate": sr,
            "observation_count": count,
            "confidence": confidence,
            "contributing_agents": [],
            "source_sequence": raw_seq[:200],
            "timestamp": _now_iso(),
            "status": "active",
        })

    return paths


def _infer_domain(files: list[str]) -> str:
    """Infer domain from files_touched paths."""
    for f in files:
        fl = f.lower()
        for domain, prefixes in _DOMAIN_MAP.items():
            if any(p in fl for p in prefixes):
                return domain
    return "general"


def get_session_patterns() -> list[dict]:
    """Mine interactive sessions for golden paths.

    Reads agent_sessions.json, filters to success sessions (learnings, no
    failures), groups by inferred domain, and returns golden path dicts.
    """
    data = load_json(SESSIONS_FILE)
    if not isinstance(data, list):
        return []

    # Filter: has learnings, no failures, no task_id (interactive only)
    success = [s for s in data
               if s.get("learnings")
               and not s.get("failures")
               and not s.get("task_id")]

    # Group by domain
    groups: dict[str, list[dict]] = defaultdict(list)
    for s in success:
        domain = _infer_domain(s.get("files_touched", []))
        groups[domain].append(s)

    paths = []
    for domain, sessions in groups.items():
        if len(sessions) < MIN_OBSERVATIONS:
            continue

        # Collect unique learnings as steps (top 5 most common)
        learning_counts: dict[str, int] = defaultdict(int)
        for s in sessions:
            for l in s.get("learnings", []):
                text = l.strip()[:120]
                if len(text) > 15:
                    learning_counts[text] += 1

        top_learnings = sorted(learning_counts.items(),
                               key=lambda x: -x[1])[:5]
        steps = [text for text, _ in top_learnings]
        if not steps:
            continue

        agents = list({s.get("agent", "") for s in sessions
                       if s.get("agent")})
        total = len(sessions)
        confidence = round(min(total / 20, 1.0), 2)

        paths.append({
            "id": _path_id(domain, f"{domain} session pattern"),
            "domain": domain,
            "title": f"{domain.title()} Interactive Pattern",
            "steps": steps,
            "success_rate": 1.0,
            "observation_count": total,
            "confidence": confidence,
            "contributing_agents": agents[:5],
            "source": "interactive_sessions",
            "timestamp": _now_iso(),
            "status": "active",
        })

    return paths


def dedup_paths(paths: list[dict]) -> list[dict]:
    """CRDT-deduplicate paths with similar titles in the same domain."""
    crdt = EvidenceCRDT()
    by_domain: dict[str, list[dict]] = defaultdict(list)
    for p in paths:
        by_domain[p.get("domain", "general")].append(p)

    deduped = []
    for domain, domain_paths in by_domain.items():
        merged = crdt.merge(domain_paths, [])
        unique = []
        for p in merged:
            is_dup = False
            for existing in unique:
                if text_similarity(
                    p.get("title", ""), existing.get("title", "")
                ) >= SIMILARITY_THRESHOLD:
                    if p.get("confidence", 0) > existing.get("confidence", 0):
                        unique.remove(existing)
                        unique.append(p)
                    is_dup = True
                    break
            if not is_dup:
                unique.append(p)
        deduped.extend(unique)

    deduped.sort(key=lambda p: -p.get("confidence", 0))
    return deduped[:MAX_PATHS]
