"""Unit tests for the cross-sectional activation momentum ranking."""

from __future__ import annotations

from datetime import date, timedelta

import pytest

from app.models.ohlcv import OHLCVRecord
from app.models.ticker import Ticker
from app.services import momentum_service as ms


@pytest.fixture
async def session():
    from tests.conftest import _test_session_factory

    async with _test_session_factory() as s:
        yield s


async def _seed(session, symbol: str, rate: float, n: int = 280) -> None:
    t = Ticker(symbol=symbol)
    session.add(t)
    await session.flush()
    base = date(2024, 1, 1)
    for i in range(n):
        close = 100.0 * (rate ** i)
        session.add(OHLCVRecord(
            ticker_id=t.id,
            date=base + timedelta(days=i),
            open=close, high=close, low=close, close=close,
            volume=1_000_000,
        ))
    await session.commit()


async def _seed_closes(session, symbol: str, closes: list[float]) -> None:
    t = Ticker(symbol=symbol)
    session.add(t)
    await session.flush()
    base = date(2024, 1, 1)
    for i, close in enumerate(closes):
        session.add(OHLCVRecord(
            ticker_id=t.id,
            date=base + timedelta(days=i),
            open=close, high=close, low=close, close=close,
            volume=1_000_000,
        ))
    await session.commit()


def test_compute_momentum_insufficient_history():
    assert ms.compute_12_1_momentum([100.0] * 100) is None


def test_compute_momentum_value():
    closes = [100.0 * (1.01 ** i) for i in range(300)]
    m = ms.compute_12_1_momentum(closes)
    # 12-1 momentum skips the last month: close[-22] / close[-253] - 1
    assert m == closes[-22] / closes[-253] - 1.0
    assert m > 0


async def test_ranks_universe_into_raw_percentiles_when_benchmark_missing(session, monkeypatch):
    async def no_benchmark(_db):
        return {}

    monkeypatch.setattr(ms, "_load_activation_benchmark", no_benchmark)
    await _seed(session, "HIGH", rate=1.010)   # strong uptrend → top momentum
    await _seed(session, "MID", rate=1.002)
    await _seed(session, "LOW", rate=0.999)     # declining → bottom momentum

    pct = await ms.compute_momentum_percentiles(session)
    assert pct["HIGH"] == 100.0
    assert pct["MID"] == 50.0
    assert pct["LOW"] == 0.0


async def test_ranks_universe_into_residual_percentiles_when_benchmark_available(session, monkeypatch):
    base = date(2024, 1, 1)
    n = 280
    benchmark = {base + timedelta(days=i): 100.0 * (1.001 ** i) for i in range(n)}

    async def with_benchmark(_db):
        return benchmark

    monkeypatch.setattr(ms, "_load_activation_benchmark", with_benchmark)
    market = [benchmark[base + timedelta(days=i)] for i in range(n)]
    await _seed_closes(session, "DRIFT", [market[i] * (1.0008 ** i) for i in range(n)])
    await _seed_closes(session, "BETA", market)
    await _seed_closes(session, "LAG", [market[i] * (0.9992 ** i) for i in range(n)])

    pct = await ms.compute_momentum_percentiles(session)
    assert pct["DRIFT"] == 100.0
    assert pct["BETA"] == 50.0
    assert pct["LAG"] == 0.0


async def test_short_history_ticker_is_unranked(session, monkeypatch):
    async def no_benchmark(_db):
        return {}

    monkeypatch.setattr(ms, "_load_activation_benchmark", no_benchmark)
    await _seed(session, "LONG", rate=1.005)
    await _seed(session, "SHORTHX", rate=1.005, n=100)  # < 1y → no momentum

    pct = await ms.compute_momentum_percentiles(session)
    assert "LONG" in pct
    assert "SHORTHX" not in pct


async def test_empty_universe_returns_empty(session, monkeypatch):
    async def no_benchmark(_db):
        return {}

    monkeypatch.setattr(ms, "_load_activation_benchmark", no_benchmark)
    assert await ms.compute_momentum_percentiles(session) == {}
