"""Fix 1 — global N flows into the gauntlet bar.

The FleetOrchestrator must pass `n_trials = store.total_trials() + local_eff` to
evaluate_by_regime so the deflated-Sharpe bar scales with the CUMULATIVE search, not
just the current territory's candidate count.

Two tests:
  1. Unit — verify the exact n_trials expression (store total + local) sums correctly.
  2. Integration — build a SqlStrategyStore, seed 5 000 prior trials, run hunt() over a
     tiny territory and assert the n_trials_at_discovery recorded on the resulting
     StrategyRecord is >= 5 000 (i.e. the global count was threaded through, not just the
     local candidate count of 1).
"""
from __future__ import annotations

import datetime as dt

import numpy as np
import pytest

from fxhnt.adapters.persistence import DuckDbAnalyticalStore, SqlStrategyStore
from fxhnt.application.fleet import FleetOrchestrator, Territory
from fxhnt.config import Settings
from fxhnt.domain.factory import StrategyStatus
from fxhnt.domain.models import AssetClass, Market, PriceSeries


# ---------------------------------------------------------------------------
# helpers
# ---------------------------------------------------------------------------

def _effective_global_trials(store_total: int, local_eff: int) -> int:
    """The expression wired into FleetOrchestrator.hunt — global prior + local territory size."""
    return store_total + local_eff


class _TrendyDataProvider:
    """Fake DataProvider that returns a price series with a strong, persistent uptrend so the
    trend strategy clears the gauntlet even at a moderate n_trials inflate."""

    name = "trendy-fake"

    def fetch(self, market: Market, start=None, end=None) -> PriceSeries:
        rng = np.random.default_rng(42)
        n = 800
        # Strong daily drift (0.2 bps per bar) + low noise → IS and OOS Sharpe both positive
        drift = 0.002
        noise = rng.normal(0, 0.005, n)
        returns = drift + noise
        close = np.cumprod(1.0 + returns) * 100.0
        dates = tuple(str(dt.date(2023, 1, 1) + dt.timedelta(days=i)) for i in range(n))
        return PriceSeries(market=market, dates=dates, close=close)


# ---------------------------------------------------------------------------
# unit test — expression correctness
# ---------------------------------------------------------------------------

def test_effective_global_trials_sums_store_and_local() -> None:
    """The n_trials expression must be store_total + local_eff, NOT just local_eff."""
    assert _effective_global_trials(5_000, 1) == 5_001
    assert _effective_global_trials(0, 3) == 3
    assert _effective_global_trials(99_999, 10) == 100_009
    # The naive (broken) behaviour: local only — must differ from the honest expression
    local_only = 1
    store_total = 5_000
    assert _effective_global_trials(store_total, local_only) != local_only


# ---------------------------------------------------------------------------
# integration test — global N flows into StrategyRecord.n_trials_at_discovery
# ---------------------------------------------------------------------------

def test_hunt_records_global_n_in_strategy_record(tmp_path) -> None:
    """After seeding 5 000 prior trials, any record written by hunt() must carry
    n_trials_at_discovery >= 5 000, proving the store total was threaded into the bar."""
    prior_trials = 5_000

    dsn = f"sqlite:///{tmp_path / 'store.db'}"
    analytical_path = str(tmp_path / "analytical.duckdb")

    settings = Settings(
        operational_dsn=dsn,
        analytical_path=analytical_path,
    )

    store = SqlStrategyStore(dsn)
    store.add_trials(prior_trials)
    assert store.total_trials() == prior_trials

    analytical = DuckDbAnalyticalStore(analytical_path)
    data = _TrendyDataProvider()

    orchestrator = FleetOrchestrator(
        data=data,
        analytical=analytical,
        store=store,
        settings=settings,
        now=dt.datetime(2025, 6, 1, tzinfo=dt.timezone.utc),
    )

    # One territory, one market, one param set → local_eff = 1; n_trials wired = 5000 + 1 = 5001
    territories = [
        Territory(
            name="trend-hunter",
            kind="trend",
            markets=["ES"],
            asset_class=AssetClass.FUTURE,
            param_grid=[{"window": 50.0}],
        )
    ]

    report = orchestrator.hunt(territories)

    # The global trial counter must have been incremented (1 new unseen candidate tested)
    assert store.total_trials() > prior_trials, (
        "store.total_trials() must grow after hunt() — add_trials was not called"
    )

    # Whether or not the candidate passes the gauntlet, verify n_trials_at_discovery
    # If it passed: n_trials_at_discovery == prior_trials + 1 (or the local_eff variant)
    # If it did NOT pass (strong uptrend should pass, but edge cases exist): check via report
    all_records = store.list()
    if all_records:
        for rec in all_records:
            assert rec.n_trials_at_discovery >= prior_trials, (
                f"n_trials_at_discovery={rec.n_trials_at_discovery} < prior_trials={prior_trials}: "
                "global N was NOT threaded into the StrategyRecord — the immune system is decorative"
            )
    else:
        # No record written (candidate failed gauntlet) — still verify the n_trials expression
        # would have been >= prior_trials by checking report.global_trials
        assert report.global_trials >= prior_trials + 1, (
            f"global_trials={report.global_trials} should be >= {prior_trials + 1} after hunting 1 candidate"
        )
