"""Agent hunter (fxhnt 2) — the LLM directs the search. Instead of fixed grids, an LLM proposes the next
batch of (market, strategy, window) candidates, reasoning about strategy-market fit and the GAPS the
current population leaves uncovered. The fleet then evaluates each regime-conditionally as usual. The LLM
chooses WHAT to hunt; the deterministic gauntlet still decides what survives. Proposals are clamped to the
real universe and known strategies — a hallucinated symbol can't enter the pipeline."""
from __future__ import annotations

from pydantic import BaseModel, Field

from fxhnt.application.fleet import Territory
from fxhnt.domain.models import AssetClass

_SYSTEM = """You are the research director at fxhnt, directing specialist hunters. Propose the next batch of
(market, strategy, window) candidates to test. Each is evaluated REGIME-CONDITIONALLY — a strategy is kept
only where it works in a specific market regime. Focus on:
- strategy-market FIT: 'trend' on futures (commodities, FX, indices); 'mean_reversion' on range-bound
  markets; 'buy_hold' as a baseline.
- LOOKBACK WINDOWS (empirical prior): 'trend' needs LONG windows (~100-250 days; shorter whipsaws and
  fails); 'mean_reversion' needs SHORT windows (~10-40 days). Choose windows accordingly.
- GAPS: markets the current population does not yet cover, or different lookback windows worth probing.
Do not repeat candidates already in the population. Propose 3-6 candidates."""


class HunterProposal(BaseModel):
    market: str = Field(description="a symbol from the given universe")
    kind: str = Field(description="trend | mean_reversion | buy_hold")
    window: int = Field(default=200, description="lookback window")
    rationale: str = Field(default="", description="why this is worth hunting")


class HunterBatch(BaseModel):
    reasoning: str = Field(default="", description="brief reasoning about where to search next")
    proposals: list[HunterProposal] = Field(description="3-6 candidates to hunt")


class AgentHunter:
    def __init__(self, llm, *, asset_class: str = "future") -> None:
        self._structured = llm.with_structured_output(HunterBatch)
        self._asset_class = AssetClass(asset_class)

    def propose_territories(self, *, universe: list[str], kinds: list[str], population: str) -> list[Territory]:
        prompt = (_SYSTEM + f"\n\nUniverse: {', '.join(universe)}\nStrategies: {', '.join(kinds)}\n"
                  f"Current population:\n{population or '(empty — nothing found yet)'}\n\nPropose the next candidates.")
        batch = self._structured.invoke(prompt)
        universe_up = {u.upper() for u in universe}
        territories: list[Territory] = []
        for p in batch.proposals:
            sym = p.market.strip().upper()
            if sym not in universe_up or p.kind not in kinds:
                continue  # clamp hallucinations to the real universe + known strategies
            territories.append(Territory(name=f"agent:{p.kind}-hunter", kind=p.kind, markets=[sym],
                                         asset_class=self._asset_class, param_grid=[{"window": float(p.window)}]))
        return territories
