"""Tests for built-in metric calculators."""

from __future__ import annotations

import pytest

from pyfortis._types import MetricSpec, MetricType, Position, Severity
from pyfortis._calculators import (
    compute_beta,
    compute_cvar,
    compute_drawdown,
    compute_sharpe,
    compute_sortino,
    compute_var,
    compute_volatility,
)


@pytest.fixture
def positions() -> dict[str, Position]:
    return {
        "AAPL": Position(
            instrument="AAPL", quantity=100, avg_price=150.0, current_price=155.0,
        ),
    }


@pytest.fixture
def returns() -> list[float]:
    return [0.01, -0.02, 0.015, -0.005, 0.008, -0.01, 0.02, -0.03, 0.005, 0.01]


class TestComputeVar:
    def test_basic(self, positions, returns):
        metric = MetricSpec(name="var", type=MetricType.VAR, params={"confidence": 0.95})
        result = compute_var(metric, positions, returns, {})
        assert result.value > 0
        assert result.metric_name == "var"

    def test_empty_returns(self, positions):
        metric = MetricSpec(name="var", type=MetricType.VAR, params={"confidence": 0.95})
        result = compute_var(metric, positions, [], {})
        assert result.value == 0.0

    def test_breach(self, positions, returns):
        metric = MetricSpec(
            name="var", type=MetricType.VAR,
            params={"confidence": 0.95, "threshold": 0.0},
        )
        result = compute_var(metric, positions, returns, {})
        assert result.breach is True


class TestComputeCVar:
    def test_basic(self, positions, returns):
        metric = MetricSpec(name="cvar", type=MetricType.CVAR, params={"confidence": 0.95})
        result = compute_cvar(metric, positions, returns, {})
        assert result.value > 0

    def test_empty_returns(self, positions):
        metric = MetricSpec(name="cvar", type=MetricType.CVAR, params={"confidence": 0.95})
        result = compute_cvar(metric, positions, [], {})
        assert result.value == 0.0


class TestComputeDrawdown:
    def test_no_drawdown(self, positions, returns):
        metric = MetricSpec(name="dd", type=MetricType.DRAWDOWN, params={"threshold": 0.10})
        result = compute_drawdown(
            metric, positions, returns,
            {"peak_value": 1000000, "current_value": 1000000},
        )
        assert result.value == 0.0
        assert result.breach is False

    def test_with_drawdown(self, positions, returns):
        metric = MetricSpec(
            name="dd", type=MetricType.DRAWDOWN,
            params={"threshold": 0.10},
            severity=Severity.WARNING,
        )
        result = compute_drawdown(
            metric, positions, returns,
            {"peak_value": 1000000, "current_value": 850000},
        )
        assert result.value == pytest.approx(0.15)
        assert result.breach is True

    def test_zero_peak(self, positions, returns):
        metric = MetricSpec(name="dd", type=MetricType.DRAWDOWN, params={"threshold": 0.10})
        result = compute_drawdown(
            metric, positions, returns,
            {"peak_value": 0, "current_value": 0},
        )
        assert result.value == 0.0


class TestComputeVolatility:
    def test_basic(self, positions, returns):
        metric = MetricSpec(
            name="vol", type=MetricType.VOLATILITY,
            params={"annualization_factor": 252},
        )
        result = compute_volatility(metric, positions, returns, {})
        assert result.value > 0

    def test_insufficient_data(self, positions):
        metric = MetricSpec(name="vol", type=MetricType.VOLATILITY, params={})
        result = compute_volatility(metric, positions, [0.01], {})
        assert result.value == 0.0


class TestComputeBeta:
    def test_basic(self, positions, returns):
        metric = MetricSpec(name="beta", type=MetricType.BETA, params={})
        bench = [0.005, -0.01, 0.008, -0.003, 0.004, -0.005, 0.01, -0.015, 0.003, 0.006]
        result = compute_beta(metric, positions, returns, {"benchmark_returns": bench})
        assert isinstance(result.value, float)

    def test_insufficient_data(self, positions):
        metric = MetricSpec(name="beta", type=MetricType.BETA, params={})
        result = compute_beta(metric, positions, [0.01], {})
        assert result.value == 1.0  # Default


class TestComputeSharpe:
    def test_basic(self, positions, returns):
        metric = MetricSpec(
            name="sharpe", type=MetricType.SHARPE,
            params={"risk_free_rate": 0.05, "annualization_factor": 252},
        )
        result = compute_sharpe(metric, positions, returns, {})
        assert isinstance(result.value, float)

    def test_insufficient_data(self, positions):
        metric = MetricSpec(name="sharpe", type=MetricType.SHARPE, params={})
        result = compute_sharpe(metric, positions, [0.01], {})
        assert result.value == 0.0


class TestComputeSortino:
    def test_basic(self, positions, returns):
        metric = MetricSpec(
            name="sortino", type=MetricType.SORTINO,
            params={"risk_free_rate": 0.05, "annualization_factor": 252},
        )
        result = compute_sortino(metric, positions, returns, {})
        assert isinstance(result.value, float)

    def test_insufficient_data(self, positions):
        metric = MetricSpec(name="sortino", type=MetricType.SORTINO, params={})
        result = compute_sortino(metric, positions, [0.01], {})
        assert result.value == 0.0
