import datetime as dt

import pytest

from flyway.domain.errors import BusinessError
from flyway.services.load_in import LoadInHeader, LoadInLine, save_load_in, set_load_in_draft
from flyway.services.load_out import LoadOutHeader, LoadOutLine, save_load_out, set_load_out_draft
from flyway.services.stock import stock
from flyway.services.transfer import TransferHeader, TransferLine, delete_local_transfers, save_local_transfer
from tests import builders as b


def _stocked(db, qty=100):
    a, z = b.client(db, "A"), b.client(db, "Z")
    p = b.product(db)
    li = save_load_in(db, b.STAFF, LoadInHeader(client_id=a.id, code="LI-1", date=b.WHEN), [LoadInLine(p.id, "B1", qty)])
    set_load_in_draft(db, b.STAFF, li.id, False)
    return a, z, li.items[0]


def _header(a, z, code="TR-1"):
    return TransferHeader(from_client_id=a.id, to_client_id=z.id, code=code, date=b.WHEN)


def test_transfer_moves_stock_between_clients(db):
    a, z, src = _stocked(db)
    out = save_local_transfer(db, b.STAFF, _header(a, z), [TransferLine(src.id, 40)])
    inn = out.transfer_tos[0]
    assert (out.code, inn.code) == ("TR-1", "TR-1")
    assert inn.date == b.WHEN + dt.timedelta(seconds=1)
    assert not out.is_draft and not inn.is_draft
    assert (stock(db, client_id=a.id), stock(db, client_id=z.id)) == (60, 40)


def test_same_client_rejected(db):
    a, z, src = _stocked(db)
    with pytest.raises(BusinessError) as e:
        save_local_transfer(db, b.STAFF, _header(a, a), [TransferLine(src.id, 1)])
    assert e.value.field == "to_client_id"


def test_transferred_stock_can_be_loaded_out_and_then_blocks_reduction(db):
    a, z, src = _stocked(db)
    out = save_local_transfer(db, b.STAFF, _header(a, z), [TransferLine(src.id, 40)])
    twin = out.transfer_tos[0].items[0]
    lo = save_load_out(db, b.STAFF, LoadOutHeader(client_id=z.id, code="LO-1", date=b.WHEN), [LoadOutLine(twin.id, 30)])
    set_load_out_draft(db, b.STAFF, lo.id, False)
    with pytest.raises(BusinessError):
        save_local_transfer(db, b.STAFF, _header(a, z), [TransferLine(src.id, 20, id=out.items[0].id)], transfer_id=out.id)


def test_changing_to_client_rejected_when_in_items_used(db):
    a, z, src = _stocked(db)
    out = save_local_transfer(db, b.STAFF, _header(a, z), [TransferLine(src.id, 40)])
    twin = out.transfer_tos[0].items[0]
    save_load_out(db, b.STAFF, LoadOutHeader(client_id=z.id, code="LO-1", date=b.WHEN), [LoadOutLine(twin.id, 1)])
    y = b.client(db, "Y")
    with pytest.raises(BusinessError):
        save_local_transfer(db, b.STAFF, _header(a, y), [TransferLine(src.id, 40, id=out.items[0].id)], transfer_id=out.id)


def test_delete_restores_and_is_refused_when_used(db):
    a, z, src = _stocked(db)
    out = save_local_transfer(db, b.STAFF, _header(a, z), [TransferLine(src.id, 40)])
    delete_local_transfers(db, b.ADMIN, [out.id])
    assert src.remaining_stock == 100
    out = save_local_transfer(db, b.STAFF, _header(a, z, "TR-2"), [TransferLine(src.id, 40)])
    twin = out.transfer_tos[0].items[0]
    save_load_out(db, b.STAFF, LoadOutHeader(client_id=z.id, code="LO-2", date=b.WHEN), [LoadOutLine(twin.id, 1)])
    with pytest.raises(BusinessError):
        delete_local_transfers(db, b.ADMIN, [out.id])


def test_edit_can_remove_a_line(db):
    a, z, src = _stocked(db)
    p2 = b.product(db, title="P2")
    li2 = save_load_in(db, b.STAFF, LoadInHeader(client_id=a.id, code="LI-2", date=b.WHEN), [LoadInLine(p2.id, "B2", 50)])
    set_load_in_draft(db, b.STAFF, li2.id, False)
    src2 = li2.items[0]
    out = save_local_transfer(db, b.STAFF, _header(a, z), [TransferLine(src.id, 40), TransferLine(src2.id, 10)])
    keep = next(i for i in out.items if i.main_item is src)
    save_local_transfer(db, b.STAFF, _header(a, z), [TransferLine(src.id, 40, id=keep.id)], transfer_id=out.id)
    assert (src.remaining_stock, src2.remaining_stock) == (60, 50)
    assert len(out.transfer_tos[0].items) == 1


def test_delete_transfer_whose_in_doc_is_linked_as_related_load_in(db):
    from flyway.models import TransferRelation
    a, z, src = _stocked(db)
    out = save_local_transfer(db, b.STAFF, _header(a, z), [TransferLine(src.id, 40)])
    lo = b.transfer(db, z, load_out=True, draft=True)
    db.add(TransferRelation(load_in=out.transfer_tos[0], owner=lo))  # 旧数据里可能存在的关联
    db.flush()
    delete_local_transfers(db, b.ADMIN, [out.id])
    db.flush()
    assert src.remaining_stock == 100
