"""Tests for cpz.execution.algos — TWAP, VWAP, Iceberg algorithms."""

from __future__ import annotations

import pytest

from cpz.execution.algos.base import AlgoState
from cpz.execution.algos.iceberg import IcebergAlgorithm
from cpz.execution.algos.twap import TWAPAlgorithm
from cpz.execution.algos.vwap import VWAPAlgorithm
from cpz.execution.enums import OrderSide, OrderType, TimeInForce
from cpz.execution.models import OrderSubmitRequest


def _order(qty=1000, symbol="AAPL", limit_price=None):
    otype = OrderType.LIMIT if limit_price else OrderType.MARKET
    return OrderSubmitRequest(
        symbol=symbol,
        side=OrderSide.BUY,
        qty=qty,
        order_type=otype,
        time_in_force=TimeInForce.DAY,
        limit_price=limit_price,
        strategy_id="test",
    )


class TestTWAP:
    def test_equal_slices(self):
        algo = TWAPAlgorithm()
        slices = algo.generate_slices(_order(qty=100), {"num_slices": 10, "duration_minutes": 10})
        assert len(slices) == 10
        total = sum(s.qty for s in slices)
        assert total == 100

    def test_remainder_distribution(self):
        algo = TWAPAlgorithm()
        slices = algo.generate_slices(_order(qty=103), {"num_slices": 10, "duration_minutes": 5})
        total = sum(s.qty for s in slices)
        assert total == 103

    def test_scheduled_times_ascending(self):
        algo = TWAPAlgorithm()
        slices = algo.generate_slices(_order(qty=100), {"num_slices": 5, "duration_minutes": 10})
        for i in range(1, len(slices)):
            assert slices[i].scheduled_time > slices[i - 1].scheduled_time

    def test_zero_qty_raises(self):
        algo = TWAPAlgorithm()
        with pytest.raises(ValueError, match="qty > 0"):
            algo.generate_slices(_order(qty=0), {"num_slices": 5})

    def test_single_slice(self):
        algo = TWAPAlgorithm()
        slices = algo.generate_slices(_order(qty=50), {"num_slices": 1, "duration_minutes": 1})
        assert len(slices) == 1
        assert slices[0].qty == 50


class TestVWAP:
    def test_default_profile(self):
        algo = VWAPAlgorithm()
        slices = algo.generate_slices(_order(qty=1000), {"duration_minutes": 60})
        total = sum(s.qty for s in slices)
        assert total == 1000
        assert len(slices) > 0

    def test_custom_profile(self):
        algo = VWAPAlgorithm()
        profile = [0.5, 0.3, 0.2]
        slices = algo.generate_slices(
            _order(qty=100),
            {"num_slices": 3, "volume_profile": profile, "duration_minutes": 30},
        )
        assert len(slices) == 3
        total = sum(s.qty for s in slices)
        assert total == 100

    def test_total_qty_preserved(self):
        algo = VWAPAlgorithm()
        slices = algo.generate_slices(
            _order(qty=777),
            {"num_slices": 5, "duration_minutes": 60},
        )
        total = sum(s.qty for s in slices)
        assert total == 777


class TestIceberg:
    def test_basic_split(self):
        algo = IcebergAlgorithm()
        slices = algo.generate_slices(_order(qty=500), {"display_qty": 100})
        assert len(slices) == 5
        assert all(s.qty == 100 for s in slices)

    def test_first_pending_rest_held(self):
        algo = IcebergAlgorithm()
        slices = algo.generate_slices(_order(qty=300), {"display_qty": 100})
        assert slices[0].status == "pending"
        assert slices[1].status == "held"
        assert slices[2].status == "held"

    def test_on_fill_releases_next(self):
        algo = IcebergAlgorithm()
        slices = algo.generate_slices(_order(qty=300), {"display_qty": 100})
        state = AlgoState(total_qty=300, slices=slices)

        algo.on_fill(state, 0, 100, 150.0)
        assert slices[1].status == "pending"
        assert slices[2].status == "held"

    def test_remainder_slice(self):
        algo = IcebergAlgorithm()
        slices = algo.generate_slices(_order(qty=250), {"display_qty": 100})
        assert len(slices) == 3
        assert slices[2].qty == 50

    def test_zero_display_qty_raises(self):
        algo = IcebergAlgorithm()
        with pytest.raises(ValueError, match="display_qty"):
            algo.generate_slices(_order(qty=500), {"display_qty": 0})

    def test_limit_order_preserved(self):
        algo = IcebergAlgorithm()
        slices = algo.generate_slices(_order(qty=200, limit_price=150.0), {"display_qty": 100})
        assert all(s.limit_price == 150.0 for s in slices)
        assert all(s.order_type == "limit" for s in slices)


class TestAlgoState:
    def test_remaining_qty(self):
        state = AlgoState(total_qty=100, filled_qty=30)
        assert state.remaining_qty == 70

    def test_is_complete(self):
        state = AlgoState(total_qty=100, filled_qty=100)
        assert state.is_complete

    def test_record_fill(self):
        from cpz.execution.algos.base import AlgoSlice

        s = AlgoSlice(seq=0, symbol="AAPL", side="buy", qty=50)
        state = AlgoState(total_qty=100, slices=[s])
        state.record_fill(0, 50, 150.0)
        assert state.filled_qty == 50
        assert state.avg_fill_price == 150.0
        assert s.status == "filled"

    def test_fill_pct(self):
        state = AlgoState(total_qty=200, filled_qty=50)
        assert state.fill_pct == 25.0
