"""Reflection orchestration — event collection, learning extraction, record creation.

Extracted from tools/learning.py (Sprint 11) to separate the core
reflection pipeline from tool registration.
"""

from __future__ import annotations

from datetime import datetime, timezone
from pathlib import Path
from typing import NamedTuple

import structlog

from trw_mcp.clients.llm import LLMClient

logger = structlog.get_logger(__name__)
from trw_mcp.models.config import get_config
from trw_mcp.models.learning import Reflection
from trw_mcp.state._paths import resolve_trw_dir
from trw_mcp.state.analytics import (
    detect_tool_sequences,
    extract_learnings_from_llm,
    extract_learnings_mechanical,
    find_repeated_operations,
    find_success_patterns,
    generate_learning_id,
    is_error_event,
    surface_validated_learnings,
)
from trw_mcp.state.llm_helpers import llm_extract_learnings
from trw_mcp.state.persistence import (
    FileEventLogger,
    FileStateReader,
    FileStateWriter,
    model_to_dict,
)


def __getattr__(name: str) -> object:
    """Backward-compat shim for removed module-level singletons (FIX-044)."""
    from trw_mcp.state._helpers import _compat_getattr

    return _compat_getattr(name)


# Named caps for mechanical extraction
_MAX_ERROR_LEARNINGS = 5
_MAX_REPEATED_OPS = 3


class ReflectionInputs(NamedTuple):
    """Collected inputs for reflection processing."""

    events: list[dict[str, object]]
    run_id: str | None
    error_events: list[dict[str, object]]
    phase_transitions: list[dict[str, object]]
    repeated_ops: list[tuple[str, int]]
    success_patterns: list[dict[str, str]]
    tool_sequences: list[dict[str, object]]
    validated_learnings: list[dict[str, object]]


def collect_reflection_inputs(
    run_path: str | None,
    trw_dir: Path,
) -> ReflectionInputs:
    """Load events and categorize them for reflection processing.

    Args:
        run_path: Optional path to run directory.
        trw_dir: Path to .trw directory.

    Returns:
        ReflectionInputs with all categorized event data.
    """
    config = get_config()
    reader = FileStateReader()

    events: list[dict[str, object]] = []
    run_id: str | None = None

    if run_path:
        resolved = Path(run_path).resolve()
        events_path = resolved / "meta" / "events.jsonl"
        if reader.exists(events_path):
            events = reader.read_jsonl(events_path)

        run_yaml = resolved / "meta" / "run.yaml"
        if reader.exists(run_yaml):
            state = reader.read_yaml(run_yaml)
            raw_id = state.get("run_id")
            run_id = raw_id if isinstance(raw_id, str) else None

    error_events = [e for e in events if is_error_event(e)]
    phase_transitions = [e for e in events if e.get("event") == "phase_transition"]
    repeated_ops = find_repeated_operations(events)
    success_patterns = find_success_patterns(events)
    tool_sequences = detect_tool_sequences(
        events,
        lookback=config.reflect_sequence_lookback,
    )
    validated_learnings = surface_validated_learnings(
        trw_dir,
        q_threshold=config.reflect_q_value_threshold,
        cold_start_threshold=config.q_cold_start_threshold,
    )

    return ReflectionInputs(
        events=events,
        run_id=run_id,
        error_events=error_events,
        phase_transitions=phase_transitions,
        repeated_ops=repeated_ops,
        success_patterns=success_patterns,
        tool_sequences=tool_sequences,
        validated_learnings=validated_learnings,
    )


def generate_reflection_learnings(
    inputs: ReflectionInputs,
    trw_dir: Path,
) -> tuple[list[dict[str, str]], bool, int]:
    """Extract new learnings via LLM or mechanical fallback + success patterns.

    Args:
        inputs: Collected reflection inputs.
        trw_dir: Path to .trw directory.

    Returns:
        Tuple of (new_learnings, llm_used, positive_count).
    """
    config = get_config()

    new_learnings: list[dict[str, str]] = []
    llm_used = False

    _llm_usage_path: Path | None = None
    if config.llm_usage_log_enabled:
        _reflect_trw_dir = resolve_trw_dir()
        _llm_usage_path = _reflect_trw_dir / config.logs_dir / config.llm_usage_log_file
    llm = LLMClient(model=config.llm_default_model, usage_log_path=_llm_usage_path)
    if inputs.events and config.llm_enabled and llm.available:  # pragma: no cover
        llm_result = llm_extract_learnings(inputs.events, llm)
        if llm_result is not None:
            llm_used = True
            new_learnings = extract_learnings_from_llm(llm_result, trw_dir)

    if not llm_used:
        new_learnings = extract_learnings_mechanical(
            inputs.error_events,
            inputs.repeated_ops,
            trw_dir,
            max_errors=_MAX_ERROR_LEARNINGS,
            max_repeated=_MAX_REPEATED_OPS,
        )

    positive_count = _append_success_pattern_learnings(
        inputs.success_patterns,
        trw_dir,
        new_learnings,
    )

    return new_learnings, llm_used, positive_count


def _append_success_pattern_learnings(
    success_patterns: list[dict[str, str]],
    trw_dir: Path,
    new_learnings: list[dict[str, str]],
) -> int:
    """No-op: success patterns are analytics data only (PRD-FIX-021).

    Previously created "Success: <event> (Nx)" learning entries, which
    accounted for ~27% telemetry noise in the knowledge base.  These are
    now tracked as analytics counters only and NOT persisted as learnings.

    Args:
        success_patterns: Success patterns (not converted to learnings).
        trw_dir: Path to .trw directory (unused).
        new_learnings: List to append new learnings to (unchanged).

    Returns:
        Always 0 — no positive learnings created from success patterns.
    """
    # Intentionally suppressed — PRD-FIX-021: telemetry bloat reduction.
    _ = success_patterns
    _ = trw_dir
    _ = new_learnings
    return 0


def create_reflection_record(
    inputs: ReflectionInputs,
    new_learnings: list[dict[str, str]],
    scope: str,
) -> Reflection:
    """Create a Reflection model from inputs and extracted learnings.

    Args:
        inputs: Collected reflection inputs.
        new_learnings: Extracted learning summaries.
        scope: Reflection scope — "session", "run", or "wave".

    Returns:
        Reflection model instance.
    """
    what_worked = _build_what_worked(inputs.phase_transitions, inputs.success_patterns)
    what_failed = _build_what_failed(inputs.error_events)
    repeated_patterns = _build_repeated_patterns(inputs.repeated_ops)

    return Reflection(
        id=generate_learning_id(),
        run_id=inputs.run_id,
        scope=scope,
        timestamp=datetime.now(timezone.utc),
        events_analyzed=len(inputs.events),
        what_worked=what_worked,
        what_failed=what_failed,
        repeated_patterns=repeated_patterns,
        new_learnings=[item["id"] for item in new_learnings],
    )


def _build_what_worked(
    phase_transitions: list[dict[str, object]],
    success_patterns: list[dict[str, str]],
) -> list[str]:
    """Build what_worked list from phase transitions and success patterns."""
    return [str(e.get("event")) for e in phase_transitions] + [p["summary"] for p in success_patterns]


def _build_what_failed(error_events: list[dict[str, object]]) -> list[str]:
    """Build what_failed list from error events."""
    return [str(e.get("event")) for e in error_events[:_MAX_ERROR_LEARNINGS]]


def _build_repeated_patterns(repeated_ops: list[tuple[str, int]]) -> list[str]:
    """Build repeated_patterns list from operation tuples."""
    return [f"{op} ({count}x)" for op, count in repeated_ops[:_MAX_REPEATED_OPS]]


def persist_reflection(
    trw_dir: Path,
    reflection: Reflection,
    run_path: str | None,
    scope: str,
    learnings_count: int,
) -> None:
    """Persist reflection record and log event to run.

    Args:
        trw_dir: Path to .trw directory.
        reflection: Reflection model to persist.
        run_path: Optional run path for event logging.
        scope: Reflection scope.
        learnings_count: Number of learnings produced.
    """
    config = get_config()
    writer = FileStateWriter()
    events = FileEventLogger(writer)

    reflection_path = (
        trw_dir / config.reflections_dir / f"{datetime.now(tz=timezone.utc).date().isoformat()}-{reflection.id}.yaml"
    )
    writer.write_yaml(reflection_path, model_to_dict(reflection))

    if run_path:
        run_events_path = Path(run_path).resolve() / "meta" / "events.jsonl"
        if run_events_path.parent.exists():
            events.log_event(
                run_events_path,
                "reflection_complete",
                {
                    "reflection_id": reflection.id,
                    "scope": scope,
                    "learnings_produced": learnings_count,
                },
            )
