from __future__ import annotations

from collections import OrderedDict

from app.agents.schemas.skills import SkillShortlistEntry
from app.agents.skills.matcher import score_text_match
from app.agents.skills.policy import choose_injection_mode, render_skill_shortlist_context
from app.agents.skills.registry import get_skill_registry
from app.services.skill_service import SkillService


class RuntimeSkillRetriever:
    def __init__(self, db):
        self.db = db

    async def shortlist(
        self,
        *,
        user_id: str,
        query_text: str,
        memory_context: str | None = None,
        retrospectives: list[dict] | None = None,
        include_learned: bool = True,
        limit: int = 3,
    ) -> list[SkillShortlistEntry]:
        deduped: "OrderedDict[str, SkillShortlistEntry]" = OrderedDict()
        retrospective_text = "\n".join(
            (item.get("summary") or item.get("summary_text") or "")
            for item in (retrospectives or [])
            if isinstance(item, dict)
        )

        service = SkillService(self.db)
        for skill in await service.list_runtime_candidates(user_id, include_learned=include_learned):
            score, matched_terms = score_text_match(
                query_text,
                skill.name,
                skill.description,
                skill.instructions,
                retrospective_text,
                memory_context,
            )
            if score <= 0:
                continue
            entry = SkillShortlistEntry(
                skill_name=skill.name,
                source="database",
                source_id=skill.id,
                scope=[skill.agent_type, skill.visibility],
                status=skill.status,
                effectiveness=skill.effectiveness,
                score=score,
                matched_terms=matched_terms,
                rationale=(
                    "Shadow skill matched current request; keep metadata-only injection."
                    if skill.status == "shadow"
                    else "Matched against DB skill metadata and instructions."
                ),
                summary=skill.description or (skill.instructions[:160] if skill.instructions else None),
                injection_mode=(
                    "metadata_only"
                    if skill.status == "shadow"
                    else choose_injection_mode(score, bool(skill.description or skill.instructions))
                ),
            )
            self._upsert(deduped, entry)

        registry = get_skill_registry()
        if not registry.list_all():
            try:
                registry.load_all()
            except Exception:
                pass

        for skill in registry.list_all():
            score, matched_terms = score_text_match(
                query_text,
                skill.name,
                skill.description,
                " ".join(skill.tags),
                " ".join(skill.triggers),
                skill.content[:400],
                retrospective_text,
                memory_context,
            )
            if score <= 0:
                continue
            entry = SkillShortlistEntry(
                skill_name=skill.name,
                source=skill.source,
                source_id=skill.source_id or skill.id,
                scope=skill.scope or list(skill.tags),
                status=skill.status,
                effectiveness=skill.effectiveness,
                score=score,
                matched_terms=matched_terms,
                rationale="Matched against local or external skill metadata.",
                summary=skill.description or skill.content[:160],
                injection_mode=choose_injection_mode(
                    score,
                    bool(skill.description or skill.content),
                ),
            )
            self._upsert(deduped, entry)

        return sorted(deduped.values(), key=lambda item: item.score, reverse=True)[:limit]

    @staticmethod
    def _upsert(
        deduped: "OrderedDict[str, SkillShortlistEntry]",
        entry: SkillShortlistEntry,
    ) -> None:
        existing = deduped.get(entry.skill_name)
        if existing is None or existing.score < entry.score:
            deduped[entry.skill_name] = entry


def build_shortlisted_skill_context(
    shortlist: list[dict] | list[SkillShortlistEntry] | None,
    *,
    agent_type: str | None = None,
) -> str:
    if not shortlist:
        return ""

    entries: list[SkillShortlistEntry] = []
    for item in shortlist:
        entry = item if isinstance(item, SkillShortlistEntry) else SkillShortlistEntry.model_validate(item)
        if agent_type and entry.scope and agent_type not in entry.scope:
            continue
        entries.append(entry)

    return render_skill_shortlist_context(entries)


async def shortlist_skills_for_request(
    db,
    *,
    user_id: str,
    user_query: str,
    memory_context: str | None = None,
    retrospectives: list[dict] | None = None,
    include_learned: bool = True,
    limit: int = 3,
) -> list[SkillShortlistEntry]:
    return await RuntimeSkillRetriever(db).shortlist(
        user_id=user_id,
        query_text=user_query,
        memory_context=memory_context,
        retrospectives=retrospectives,
        include_learned=include_learned,
        limit=limit,
    )
