from __future__ import annotations

import numpy as np
import pytest
from conftest import make_account, make_market

from signalab.account import Action, OpenPosition


def _build_account_with_positions() -> tuple:
    account = make_account(balance=10_000.0, leverage=100.0)
    market = make_market(bid=1.10000, ask=1.10010)
    account._set_current_market(market)

    buy_small = account.open(Action.BUY, size=0.01)
    sell_mid = account.open(Action.SELL, size=0.02)
    buy_large = account.open(Action.BUY, size=0.05)

    return account, market, buy_small, sell_mid, buy_large


class TestOpenPositionsAPI:
    def test_get_open_position_returns_expected_fields(self) -> None:
        account, _, buy_small, _, _ = _build_account_with_positions()

        position = account.get_open_position(buy_small)

        assert isinstance(position, OpenPosition)
        assert position.id == buy_small
        assert position.is_buy is True
        assert position.open_ask == pytest.approx(1.10010)
        assert position.open_bid == pytest.approx(1.10000)
        assert position.size == pytest.approx(0.01)

    def test_get_open_positions_without_filters_returns_all(self) -> None:
        account, _, buy_small, sell_mid, buy_large = _build_account_with_positions()

        positions = account.get_open_positions()
        ids = [p.id for p in positions]

        assert ids == [buy_small, sell_mid, buy_large]

    def test_get_open_positions_filters_by_is_buy(self) -> None:
        account, _, buy_small, sell_mid, buy_large = _build_account_with_positions()

        buy_positions = account.get_open_positions(is_buy=True)
        sell_positions = account.get_open_positions(is_buy=False)

        assert [p.id for p in buy_positions] == [buy_small, buy_large]
        assert [p.id for p in sell_positions] == [sell_mid]

    def test_get_open_positions_filters_by_ids(self) -> None:
        account, _, buy_small, sell_mid, buy_large = _build_account_with_positions()

        positions = account.get_open_positions(ids=[buy_large, buy_small])

        # Selection follows internal open order, not input ids order.
        assert [p.id for p in positions] == [buy_small, buy_large]
        assert sell_mid not in [p.id for p in positions]

    def test_get_open_positions_filters_by_ranges_and_limit(self) -> None:
        account, _, buy_small, sell_mid, buy_large = _build_account_with_positions()

        positions = account.get_open_positions(min_size=0.015, max_size=0.05, limit=1)

        assert len(positions) == 1
        assert positions[0].id == sell_mid
        assert positions[0].id != buy_small
        assert positions[0].id != buy_large

    def test_get_open_positions_negative_limit_raises(self) -> None:
        account, _, _, _, _ = _build_account_with_positions()

        with pytest.raises(ValueError, match="limit"):
            account.get_open_positions(limit=-1)

    def test_batch_get_open_positions_alias_matches_main_method(self) -> None:
        account, _, _, _, _ = _build_account_with_positions()

        expected = account.get_open_positions(is_buy=True, min_size=0.01)
        got = account.batch_get_open_positions(is_buy=True, min_size=0.01)

        assert got == expected


class TestBatchPropertyQueries:
    def test_property_queries_support_single_id(self) -> None:
        account, _, buy_small, _, _ = _build_account_with_positions()

        assert np.array_equal(account.position_directions(buy_small), np.array([True]))
        assert np.allclose(account.position_open_asks(buy_small), np.array([1.10010]))
        assert np.allclose(account.position_open_bids(buy_small), np.array([1.10000]))
        assert np.allclose(account.position_sizes(buy_small), np.array([0.01]))

    def test_property_queries_preserve_input_ids_order(self) -> None:
        account, _, buy_small, sell_mid, buy_large = _build_account_with_positions()
        query_ids = [buy_large, buy_small, sell_mid]

        directions = account.position_directions(query_ids)
        sizes = account.position_sizes(query_ids)

        assert np.array_equal(directions, np.array([True, True, False]))
        assert np.allclose(sizes, np.array([0.05, 0.01, 0.02]))

    def test_property_queries_missing_id_raises(self) -> None:
        account, _, buy_small, _, _ = _build_account_with_positions()

        with pytest.raises(ValueError, match="Position IDs not found"):
            account.position_values([buy_small, 99999])

    def test_property_queries_empty_ids_raises(self) -> None:
        account, _, _, _, _ = _build_account_with_positions()

        with pytest.raises(ValueError, match="at least one"):
            account.position_margins([])


class TestBatchClose:
    def test_batch_close_with_filters_closes_selected_only(self) -> None:
        account, market, buy_small, sell_mid, buy_large = (
            _build_account_with_positions()
        )

        account._set_current_market(market)

        closed_ids = account.batch_close(is_buy=True, max_size=0.02)

        assert closed_ids == [buy_small]
        remaining = account.get_open_position_ids().tolist()
        assert remaining == [buy_large, sell_mid] or remaining == [sell_mid, buy_large]
