"""Tests for the user-curated watchlist service.

The watchlist is fully manual: the user adds and removes tickers, capped at
WATCHLIST_MAX. Removals are hard deletes and must stick. Entries are enriched
on read with the latest price and day-over-day move.
"""

from __future__ import annotations

from datetime import date, datetime, timedelta, timezone

import pytest

from app.exceptions import DuplicateError, NotFoundError, ValidationError
from app.models.ohlcv import OHLCVRecord
from app.models.score import CompositeScore
from app.models.ticker import Ticker
from app.models.user import User
from app.services.watchlist_service import (
    WATCHLIST_MAX,
    add_manual_entry,
    get_watchlist,
    remove_entry,
)

# Plain (committing) session — the watchlist service commits, so the rolling-back
# db_session fixture would fight it. Reuse the shared in-memory engine.
from tests.conftest import _test_session_factory  # type: ignore


@pytest.fixture
async def session():
    async with _test_session_factory() as s:
        yield s


async def _make_user(session) -> int:
    user = User(username="u", password_hash="x", role="user", has_access=True)
    session.add(user)
    await session.flush()
    await session.commit()
    return user.id


async def _make_ticker(session, symbol: str, *, score: float | None = None) -> int:
    t = Ticker(symbol=symbol)
    session.add(t)
    await session.flush()
    if score is not None:
        session.add(
            CompositeScore(
                ticker_id=t.id,
                score=score,
                is_stale=False,
                weights_json="{}",
                computed_at=datetime.now(timezone.utc),
            )
        )
    await session.commit()
    return t.id


async def test_enrich_includes_momentum_percentile(session):
    """The watchlist row carries the ticker's momentum percentile (from its setup),
    which replaces the old S/R-levels column in the UI."""
    from app.models.trade_setup import TradeSetup

    user_id = await _make_user(session)
    tid = await _make_ticker(session, "AAA", score=70.0)
    session.add(TradeSetup(
        ticker_id=tid, direction="long", entry_price=100.0, stop_loss=95.0,
        target=110.0, rr_ratio=2.0, composite_score=70.0,
        momentum_percentile=88.0, detected_at=datetime.now(timezone.utc),
    ))
    await session.commit()

    await add_manual_entry(session, user_id, "AAA")
    rows = await get_watchlist(session, user_id)
    assert rows[0]["momentum_percentile"] == 88.0


async def test_add_and_remove_sticks(session):
    user_id = await _make_user(session)
    await _make_ticker(session, "AAA", score=80.0)

    await add_manual_entry(session, user_id, "AAA")
    rows = await get_watchlist(session, user_id)
    assert {r["symbol"] for r in rows} == {"AAA"}

    # Removal is permanent — nothing re-adds it.
    await remove_entry(session, user_id, "AAA")
    rows = await get_watchlist(session, user_id)
    assert rows == []


async def test_duplicate_add_rejected(session):
    user_id = await _make_user(session)
    await _make_ticker(session, "AAA")
    await add_manual_entry(session, user_id, "AAA")
    with pytest.raises(DuplicateError):
        await add_manual_entry(session, user_id, "AAA")


async def test_remove_absent_raises(session):
    user_id = await _make_user(session)
    await _make_ticker(session, "AAA")
    with pytest.raises(NotFoundError):
        await remove_entry(session, user_id, "AAA")


async def test_cap_enforced(session):
    user_id = await _make_user(session)
    for i in range(WATCHLIST_MAX):
        sym = f"T{i:02d}"
        await _make_ticker(session, sym)
        await add_manual_entry(session, user_id, sym)

    await _make_ticker(session, "OVER")
    with pytest.raises(ValidationError):
        await add_manual_entry(session, user_id, "OVER")


async def test_enrichment_includes_price_and_change(session):
    user_id = await _make_user(session)
    ticker_id = await _make_ticker(session, "AAA", score=70.0)

    today = date.today()
    session.add(OHLCVRecord(
        ticker_id=ticker_id, date=today - timedelta(days=1),
        open=100, high=100, low=100, close=100.0, volume=1,
    ))
    session.add(OHLCVRecord(
        ticker_id=ticker_id, date=today,
        open=110, high=110, low=110, close=110.0, volume=1,
    ))
    await session.commit()

    await add_manual_entry(session, user_id, "AAA")
    rows = await get_watchlist(session, user_id)
    row = rows[0]
    assert row["last_close"] == 110.0
    assert row["change_pct"] == pytest.approx(10.0)
    assert row["price_date"] == today
