from unittest.mock import MagicMock, PropertyMock, call, patch

import pytest
from django.conf import settings

from wbportfolio.order_routing.model import ExecutionStatus, Order
from wbportfolio.order_routing.router import Router


@pytest.fixture
def mock_adapter():
    adapter = MagicMock()
    return adapter


@pytest.fixture
def router(mock_adapter):
    return Router(adapter=mock_adapter)


def test_submit_as_draft_from_settings(monkeypatch, router):
    # Test default True if settings attribute missing
    monkeypatch.setattr(settings, "ORDER_ROUTING_AS_DRAFT", True)
    monkeypatch.setattr(settings, "DEBUG", False)
    assert router.submit_as_draft is True

    monkeypatch.setattr(settings, "ORDER_ROUTING_AS_DRAFT", False)
    monkeypatch.setattr(settings, "DEBUG", True)
    assert router.submit_as_draft is True

    monkeypatch.setattr(settings, "ORDER_ROUTING_AS_DRAFT", True)
    monkeypatch.setattr(settings, "DEBUG", True)
    assert router.submit_as_draft is True

    monkeypatch.setattr(settings, "ORDER_ROUTING_AS_DRAFT", False)
    monkeypatch.setattr(settings, "DEBUG", False)
    assert router.submit_as_draft is False


@patch.object(Router, "submit_as_draft", new_callable=PropertyMock)
def test_submit_rebalancing_calls_adapter_as_draft(mock_property, router, mock_adapter):
    mock_property.return_value = True

    orders = [MagicMock(spec=Order), MagicMock(spec=Order)]
    orders_gen = iter(orders)
    serialized_orders = [  # list[dict] from serialize_order
        {"userElementId": "1", "targetWeight": "25.5"},
        {"userElementId": "2", "targetWeight": "10.0"},
    ]
    serialized_orders_gen = iter(serialized_orders)

    confirmed_items = [  # raw list[dict] from submit_rebalancing
        {"userElementId": "1", "status": "confirmed"},
        {"userElementId": "2", "status": "confirmed"},
    ]
    msg = "Success message"

    # New mocks for single-order methods
    mock_adapter.serialize_order.side_effect = lambda order: next(serialized_orders_gen)  # dict per order
    mock_adapter.submit_rebalancing.return_value = (confirmed_items, msg, {"id": "reb123"})
    mock_adapter.deserialize_item.side_effect = lambda item: next(orders_gen)

    rebalancing_result = router.submit_rebalancing(orders)
    assert rebalancing_result.orders == orders
    assert rebalancing_result.status == ExecutionStatus.IN_DRAFT
    assert rebalancing_result.message == msg

    mock_adapter.serialize_order.assert_has_calls([call(orders[0]), call(orders[1])], any_order=False)
    mock_adapter.submit_rebalancing.assert_called_once_with(serialized_orders, as_draft=True)
    mock_adapter.deserialize_item.assert_has_calls(
        [call(confirmed_items[0]), call(confirmed_items[1])], any_order=False
    )


@patch.object(Router, "submit_as_draft", new_callable=PropertyMock)
def test_submit_rebalancing_calls_adapter(mock_property, router, mock_adapter):
    mock_property.return_value = False
    orders = [MagicMock(spec=Order), MagicMock(spec=Order)]
    orders_gen = iter(orders)
    serialized_orders = [  # list[dict] from serialize_order
        {"userElementId": "1", "targetWeight": "25.5"},
        {"userElementId": "2", "targetWeight": "10.0"},
    ]
    serialized_orders_gen = iter(serialized_orders)

    confirmed_items = [  # raw list[dict] from submit_rebalancing
        {"userElementId": "1", "status": "confirmed"},
        {"userElementId": "2", "status": "confirmed"},
    ]
    msg = "Success message"

    # New mocks for single-order methods
    mock_adapter.serialize_order.side_effect = lambda order: next(serialized_orders_gen)  # dict per order
    mock_adapter.submit_rebalancing.return_value = (confirmed_items, msg, {"id": "reb123"})
    mock_adapter.deserialize_item.side_effect = lambda item: next(orders_gen)

    rebalancing_result = router.submit_rebalancing(orders)

    assert rebalancing_result.orders == orders
    assert rebalancing_result.status == ExecutionStatus.PENDING
    assert rebalancing_result.message == msg

    mock_adapter.serialize_order.assert_has_calls([call(orders[0]), call(orders[1])], any_order=False)
    mock_adapter.submit_rebalancing.assert_called_once_with(serialized_orders, as_draft=False)
    mock_adapter.deserialize_item.assert_has_calls(
        [call(confirmed_items[0]), call(confirmed_items[1])], any_order=False
    )


def test_get_rebalance_status_returns_adapter_status(router, mock_adapter):
    expected_status = ExecutionStatus.PENDING
    expected_msg = "Status message"
    mock_adapter.get_rebalance_status.return_value = (expected_status, expected_msg)

    status, msg = router.get_rebalance_status()
    assert status == expected_status
    assert msg == expected_msg
    mock_adapter.get_rebalance_status.assert_called_once()


def test_cancel_rebalancing_returns_adapter_result(router, mock_adapter):
    mock_adapter.cancel_current_rebalancing.return_value = True
    result = router.cancel_rebalancing()
    assert result is True
    mock_adapter.cancel_current_rebalancing.assert_called_once()


def test_get_current_rebalancing_request_returns_deserialized(router, mock_adapter):
    serialized_orders = ["order1", "order2"]
    deserialized_orders = [MagicMock(spec=Order), MagicMock(spec=Order)]
    mock_adapter.get_current_rebalancing.return_value = serialized_orders
    mock_adapter.deserialize_items.return_value = deserialized_orders

    result = router.get_current_rebalancing_request()
    assert result == deserialized_orders
    mock_adapter.get_current_rebalancing.assert_called_once()
    mock_adapter.deserialize_items.assert_called_once_with(serialized_orders)
