"""
fw_server/memory_bank.py — Structured knowledge export to memory-bank/ markdown files.

Syncs the HCP protocol state to human-readable + AI-readable markdown files
in the workspace.  This is the bridge between the formal HCP protocol and
the project's existing memory-bank system (see .instructions.md).

Design:
  - Reads HCP state (chunks, seed, workflows) and renders to markdown
  - Writes to FW_STATE_DIR/../memory-bank/ (sibling of .hcp_state/)
  - Files: activeContext.md, progress.md, techContext.md, sessionLog.md
  - AX27 (Transparent Vessel): memory_bank mediates but doesn't originate
  - KV₃ (Observer non-interference): export reads, never mutates HCP state
  - stdlib-only (no third-party deps)

Usage:
    Called explicitly via hcp_sync_memory_bank MCP tool or
    automatically during lifespan shutdown (after HCP save).
"""

from __future__ import annotations

import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

logger = logging.getLogger("fw_server.memory_bank")

from fw_server.tracer import trace


@trace
def sync_memory_bank(
    state_dir: Path,
    protocol: object,  # hcp.protocol.HCPProtocol
    *,
    session_note: str = "",
    ledger: object = None,  # fw_server.session.SessionLedger (optional)
) -> Dict[str, Any]:
    """Export HCP context to memory-bank/ markdown files.

    Args:
        state_dir: The .hcp_state/ directory (FW_STATE_DIR).
        protocol: The HCPProtocol singleton.
        session_note: Optional free-text note to append to sessionLog.
        ledger: Optional SessionLedger for tool call analytics.

    Returns:
        Dict with files_written list and summary.
    """
    # memory-bank/ lives as sibling of .hcp_state/
    mb_dir = state_dir.parent / "memory-bank"
    mb_dir.mkdir(parents=True, exist_ok=True)

    now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
    files_written: List[str] = []

    # ── 1. activeContext.md ──
    ac_path = mb_dir / "activeContext.md"
    ac_content = _render_active_context(protocol, now)
    ac_path.write_text(ac_content, encoding="utf-8")
    files_written.append(str(ac_path))

    # ── 2. techContext.md (HCP diagnostics snapshot) ──
    tc_path = mb_dir / "techContext.md"
    tc_content = _render_tech_context(protocol, now)
    tc_path.write_text(tc_content, encoding="utf-8")
    files_written.append(str(tc_path))

    # ── 3. sessionLog.md (append-only) ──
    sl_path = mb_dir / "sessionLog.md"
    sl_entry = _render_session_log_entry(protocol, now, session_note, ledger=ledger)
    if sl_path.exists():
        existing = sl_path.read_text(encoding="utf-8")
        sl_path.write_text(existing + "\n" + sl_entry, encoding="utf-8")
    else:
        header = "# Session Log\n\nAuto-generated by fw-engine memory bank sync.\n\n"
        sl_path.write_text(header + sl_entry, encoding="utf-8")
    files_written.append(str(sl_path))

    # ── 4. progress.md (workflow state) ──
    pg_path = mb_dir / "progress.md"
    pg_content = _render_progress(protocol, now)
    pg_path.write_text(pg_content, encoding="utf-8")
    files_written.append(str(pg_path))

    logger.info("Memory bank synced → %s (%d files)", mb_dir, len(files_written))

    return {
        "action": "sync_memory_bank",
        "memory_bank_dir": str(mb_dir),
        "files_written": files_written,
        "timestamp": now,
        "n_chunks": len(getattr(protocol, "_chunks", [])),
    }


# ---------------------------------------------------------------------------
# Renderers
# ---------------------------------------------------------------------------

@trace
def _render_active_context(protocol: object, timestamp: str) -> str:
    """Render activeContext.md — current HCP focus and seed summary."""
    chunks = getattr(protocol, "_chunks", [])
    seed = getattr(protocol, "_seed", None)
    is_ready = getattr(protocol, "is_ready", False)

    lines = [
        "# Active Context",
        "",
        f"> Auto-synced by fw-engine at {timestamp}",
        "",
        "## HCP Status",
        "",
        f"- **Ready**: {is_ready}",
        f"- **Chunks ingested**: {len(chunks)}",
    ]

    if seed is not None:
        lines.extend([
            f"- **Coverage ratio**: {seed.coverage_ratio:.3f}",
            f"- **Global keywords**: {', '.join(seed.global_keywords[:15])}",
            f"- **Has structure**: {seed.has_structure}",
            "",
            "## Seed Type Signature",
            "",
        ])
        # Map type signature to content types
        from hcp.types import ContentType
        ct_names = [ct.value for ct in ContentType]
        for i, (name, val) in enumerate(zip(ct_names, seed.type_signature)):
            if val > 0.01:
                lines.append(f"- **{name}**: {val:.2%}")
    else:
        lines.extend(["", "_No seed computed — no context ingested yet._"])

    # Recent chunks summary (last 5)
    if chunks:
        lines.extend(["", "## Recent Context (last 5 chunks)", ""])
        for chunk in chunks[-5:]:
            preview = chunk.content[:120].replace("\n", " ")
            lines.append(f"- `[{chunk.position}]` {preview}...")

    lines.append("")
    return "\n".join(lines)


@trace
def _render_tech_context(protocol: object, timestamp: str) -> str:
    """Render techContext.md — HCP configuration and diagnostics."""
    config = getattr(protocol, "config", None)
    chunks = getattr(protocol, "_chunks", [])

    lines = [
        "# Technical Context",
        "",
        f"> Auto-synced by fw-engine at {timestamp}",
        "",
        "## HCP Configuration",
        "",
    ]

    if config is not None:
        lines.extend([
            f"- **Chunk size**: {config.chunk_size}",
            f"- **Seed top-K keywords**: {config.seed_top_k}",
            f"- **Attention decay**: {config.attention_decay}",
            f"- **Seed boost factor**: {config.seed_boost_factor}",
        ])
    else:
        lines.append("_Default configuration._")

    lines.extend([
        "",
        "## Context Statistics",
        "",
        f"- **Total chunks**: {len(chunks)}",
    ])

    if chunks:
        # Content type distribution
        from collections import Counter
        ct_dist = Counter(c.content_type.value for c in chunks)
        lines.extend(["", "### Content Type Distribution", ""])
        for ct, count in ct_dist.most_common():
            lines.append(f"- **{ct}**: {count}")

        # Source metadata summary
        sources = Counter(
            c.source_metadata.tool_name
            for c in chunks
            if c.source_metadata and c.source_metadata.tool_name
        )
        if sources:
            lines.extend(["", "### Ingestion Sources", ""])
            for src, count in sources.most_common():
                lines.append(f"- **{src}**: {count} chunks")

    lines.append("")
    return "\n".join(lines)


@trace
def _render_session_log_entry(
    protocol: object, timestamp: str, note: str,
    *, ledger: object = None,
) -> str:
    """Render a single session log entry with optional tool-call analytics."""
    chunks = getattr(protocol, "_chunks", [])
    seed = getattr(protocol, "_seed", None)
    workflows = getattr(protocol, "_workflows", {})

    lines = [
        f"### {timestamp}",
        "",
        f"- **Chunks**: {len(chunks)}",
    ]

    if seed is not None:
        lines.append(f"- **Coverage**: {seed.coverage_ratio:.3f}")
        lines.append(f"- **Keywords**: {', '.join(seed.global_keywords[:10])}")

    if workflows:
        active_wf = sum(1 for ws in workflows.values() if not ws.is_complete)
        done_wf = sum(1 for ws in workflows.values() if ws.is_complete)
        lines.append(f"- **Workflows**: {active_wf} active, {done_wf} complete")

    # ── Session ledger analytics (AX57 — transparency across sessions) ──
    if ledger is not None:
        call_count = getattr(ledger, "call_count", 0)
        if call_count > 0:
            lines.extend([
                "",
                "#### Tool Call Analytics",
                "",
                f"- **Total calls**: {call_count}",
                f"- **Grade ceiling**: {getattr(ledger, 'grade_ceiling', None) or 'N/A'}",
            ])
            anomalies = getattr(ledger, "anomalies", [])
            if anomalies:
                lines.append(f"- **Anomalies**: {', '.join(anomalies[:10])}")
            gate_history = getattr(ledger, "gate_history", [])
            if gate_history:
                lines.append(f"- **Gates fired**: {', '.join(gate_history[:10])}")
            # Tool frequency breakdown from calls
            calls = getattr(ledger, "_calls", [])
            if calls:
                from collections import Counter
                tool_freq = Counter(c.tool_name for c in calls)
                top_tools = tool_freq.most_common(5)
                lines.append(f"- **Top tools**: {', '.join(f'{t}({n})' for t, n in top_tools)}")

    if note:
        lines.extend(["", f"**Note**: {note}"])

    lines.append("")
    return "\n".join(lines)


@trace
def _render_progress(protocol: object, timestamp: str) -> str:
    """Render progress.md — workflow progress tracking."""
    workflows = getattr(protocol, "_workflows", {})

    lines = [
        "# Progress",
        "",
        f"> Auto-synced by fw-engine at {timestamp}",
        "",
    ]

    if not workflows:
        lines.append("_No active workflows._")
        lines.append("")
        return "\n".join(lines)

    for wid, ws in workflows.items():
        status = "✅ Complete" if ws.is_complete else "🔄 In Progress"
        lines.extend([
            f"## Workflow: {wid}",
            "",
            f"- **Status**: {status}",
            f"- **Stages**: {len(ws.stages)}",
            f"- **Current**: {ws.current_index}/{len(ws.stages)}",
            "",
        ])

        for i, stage in enumerate(ws.stages):
            if i < ws.current_index:
                marker = "✅"
            elif i == ws.current_index and not ws.is_complete:
                marker = "🔄"
            else:
                marker = "⬜"
            lines.append(f"  {marker} Stage {i}: {stage.name}")

        lines.extend(["", ""])

    return "\n".join(lines)
