"""Knowledge retrieval — domain, structural, and keyword search."""

from __future__ import annotations

import re

from fliiq.runtime.knowledge.drift import check_drift
from fliiq.runtime.knowledge.models import KnowledgeRecord
from fliiq.runtime.knowledge.store import KnowledgeStore


def retrieve_by_domain(
    store: KnowledgeStore,
    domain: str,
    max_records: int = 10,
) -> list[KnowledgeRecord]:
    """Load active records tagged with a domain. Sorted by confidence desc, then recency."""
    ids = store.by_domain(domain)
    records = [r for rid in ids if (r := store.load(rid)) is not None and not r.expired]
    records.sort(key=lambda r: (-r.confidence, r.created_at.timestamp()), reverse=False)
    return records[:max_records]


def retrieve_by_file(
    store: KnowledgeStore,
    file_path: str,
    line_range: tuple[int, int] | None = None,
    max_records: int = 5,
) -> list[tuple[KnowledgeRecord, dict]]:
    """Load records anchored to a file. Returns (record, drift_info) tuples.

    If line_range provided, filters to records whose anchor overlaps the range.
    """
    ids = store.by_anchor_file(file_path)
    results: list[tuple[KnowledgeRecord, dict]] = []

    for rid in ids:
        record = store.load(rid)
        if record is None or record.expired:
            continue

        # Filter by line range overlap if specified
        if line_range and record.anchor:
            anchor_start = record.anchor.line_start or 1
            anchor_end = record.anchor.line_end or 999999
            if anchor_end < line_range[0] or anchor_start > line_range[1]:
                continue

        drift_info = check_drift(record)
        results.append((record, drift_info))

    # Sort: current records first, then drifted, then missing
    priority = {"current": 0, "drifted": 1, "unanchored": 2, "missing": 3}
    results.sort(key=lambda x: (priority.get(x[1]["status"], 9), -x[0].confidence))
    return results[:max_records]


def search_knowledge(
    store: KnowledgeStore,
    query: str,
    max_results: int = 10,
    domain: str | None = None,
    record_type: str | None = None,
    tags: list[str] | None = None,
) -> list[KnowledgeRecord]:
    """Keyword search across records. Scores by word overlap with decision, rationale, context, tags."""
    query_words = set(re.findall(r"\b\w{3,}\b", query.lower()))
    if not query_words:
        return []

    # Start with filtered candidates if domain/type specified
    if domain:
        candidates = retrieve_by_domain(store, domain, max_records=100)
    else:
        candidates = store.list_records(limit=200)

    if record_type:
        candidates = [r for r in candidates if r.type.value == record_type]

    if tags:
        tag_set = set(t.lower() for t in tags)
        candidates = [r for r in candidates if tag_set & set(t.lower() for t in r.tags)]

    scored: list[tuple[int, KnowledgeRecord]] = []
    for record in candidates:
        # Build searchable text from all relevant fields
        text = " ".join([
            record.decision,
            record.rationale,
            record.context,
            record.domain,
            " ".join(record.tags),
            " ".join(record.constraints),
        ]).lower()
        text_words = set(re.findall(r"\b\w{3,}\b", text))
        overlap = len(query_words & text_words)

        # Boost by confidence
        if overlap > 0:
            score = overlap + int(record.confidence * 3)
            scored.append((score, record))

    scored.sort(key=lambda x: -x[0])
    return [record for _, record in scored[:max_results]]


def format_records_for_prompt(
    records: list[KnowledgeRecord] | list[tuple[KnowledgeRecord, dict]],
    max_chars: int = 3000,
) -> str:
    """Render records as concise text for prompt injection."""
    lines: list[str] = []
    total = 0

    for item in records:
        if isinstance(item, tuple):
            record, drift_info = item
            drift_tag = ""
            if drift_info.get("status") == "drifted":
                drift_tag = " [DRIFTED — code changed since capture]"
            elif drift_info.get("status") == "missing":
                drift_tag = " [MISSING — anchored file no longer exists]"
        else:
            record = item
            drift_tag = ""

        parts = [f"[{record.type.value}]"]
        parts.append(f"Context: {record.context}")
        parts.append(f"Decision: {record.decision}")
        parts.append(f"Rationale: {record.rationale}")
        parts.append(f"Confidence: {record.confidence}")

        if record.domain:
            parts.append(f"Domain: {record.domain}")
        if record.tags:
            parts.append(f"Tags: {', '.join(record.tags)}")
        if record.constraints:
            parts.append(f"Constraints: {', '.join(record.constraints)}")
        if record.anchor:
            anchor_desc = record.anchor.file
            if record.anchor.line_start:
                anchor_desc += f":{record.anchor.line_start}"
                if record.anchor.line_end:
                    anchor_desc += f"-{record.anchor.line_end}"
            parts.append(f"Anchored: {anchor_desc}")

        line = " | ".join(parts) + drift_tag
        line = f"- {line} (id: {record.id})"

        if total + len(line) > max_chars:
            break
        lines.append(line)
        total += len(line) + 1

    return "\n".join(lines)
