"""Composite scoring: sentiment applied as a signed adjustment around neutral,
not averaged in. Going from no sentiment to bullish must never lower the score."""

from __future__ import annotations

from datetime import datetime, timezone

import pytest
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine

from app.database import Base
from app.models.score import DimensionScore
from app.models.ticker import Ticker
from app.services import scoring_service as svc


@pytest.fixture
async def db():
    engine = create_async_engine("sqlite+aiosqlite://", echo=False)
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
    async with factory() as session:
        yield session
    await engine.dispose()


# Non-sentiment dims all at 78 → base = 78 at any positive weights.
BASE_DIMS = {"technical": 78.0, "sr_quality": 78.0, "fundamental": 78.0, "momentum": 78.0}


async def _seed(session, dims: dict[str, float]) -> None:
    ticker = Ticker(symbol="AAA")
    session.add(ticker)
    await session.flush()
    now = datetime.now(timezone.utc)
    for dim, score in dims.items():
        session.add(DimensionScore(
            ticker_id=ticker.id, dimension=dim, score=score, is_stale=False, computed_at=now
        ))
    await session.flush()


def test_sentiment_adjustment_formula():
    # weight 0.10 → MAX_ADJ = 10 points
    assert svc._sentiment_adjustment(None, 0.10) == 0.0
    assert svc._sentiment_adjustment(50.0, 0.10) == 0.0          # neutral / coin-flip
    assert svc._sentiment_adjustment(75.0, 0.10) == pytest.approx(5.0)   # bullish 75%
    assert svc._sentiment_adjustment(100.0, 0.10) == pytest.approx(10.0)
    assert svc._sentiment_adjustment(25.0, 0.10) == pytest.approx(-5.0)  # bearish 75%
    assert svc._sentiment_adjustment(0.0, 0.10) == pytest.approx(-10.0)


async def test_no_sentiment_equals_base(db):
    await _seed(db, BASE_DIMS)
    composite, missing = await svc.compute_composite_score(db, "AAA")
    assert composite == pytest.approx(78.0)
    assert "sentiment" in missing


async def test_bullish_raises_above_base(db):
    await _seed(db, {**BASE_DIMS, "sentiment": 75.0})  # bullish, 75% confidence
    composite, _ = await svc.compute_composite_score(db, "AAA")
    assert composite == pytest.approx(83.0)  # 78 + 5 — the whole point


async def test_neutral_leaves_base_unchanged(db):
    await _seed(db, {**BASE_DIMS, "sentiment": 50.0})
    composite, _ = await svc.compute_composite_score(db, "AAA")
    assert composite == pytest.approx(78.0)


async def test_bearish_lowers_base(db):
    await _seed(db, {**BASE_DIMS, "sentiment": 25.0})  # bearish, 75% confidence
    composite, _ = await svc.compute_composite_score(db, "AAA")
    assert composite == pytest.approx(73.0)  # 78 - 5


async def test_only_sentiment_uses_neutral_base(db):
    await _seed(db, {"sentiment": 75.0})
    composite, _ = await svc.compute_composite_score(db, "AAA")
    assert composite == pytest.approx(55.0)  # base 50 + 5


async def test_no_dimensions_returns_none(db):
    ticker = Ticker(symbol="AAA")
    db.add(ticker)
    await db.flush()
    composite, missing = await svc.compute_composite_score(db, "AAA")
    assert composite is None
    assert len(missing) == 5
