import datetime as dt

import pytest

from flyway.domain.errors import BusinessError, PermissionDenied
from flyway.models import Supplier
from flyway.services.packing import (
    PackingSlipHeader, delete_packing_slips, pack_load_outs, save_packing_slip, set_packing_slip_completed,
    unpack_load_outs,
)
from tests import builders as b

NOW = dt.datetime(2026, 4, 1, 2, 30, tzinfo=dt.UTC)


def _load_out(db, c, to_rmp="R9", draft=False):
    t = b.transfer(db, c, load_out=True, draft=draft)
    t.to_location, t.to_rmp = "Port", to_rmp
    return t


def test_pack_creates_slip_with_defaults_and_resets_dates(db):
    c = b.client(db)
    lo = _load_out(db, c)
    slip = pack_load_outs(db, b.STAFF, [lo.id], "PS-1", NOW)
    assert (slip.from_location, slip.from_rmp, slip.to_rmp, slip.date) == ("Flyway", "S2142", "R9", NOW)
    assert lo.packing_slip_id == slip.id and lo.date == NOW


def test_pack_rejects_packed_drafts_and_mixed_rmp(db):
    c = b.client(db)
    lo1, lo2 = _load_out(db, c), _load_out(db, c, to_rmp="OTHER")
    with pytest.raises(BusinessError):
        pack_load_outs(db, b.STAFF, [lo1.id, lo2.id], "PS-1", NOW)
    with pytest.raises(BusinessError):
        pack_load_outs(db, b.STAFF, [_load_out(db, c, draft=True).id], "PS-1", NOW)
    pack_load_outs(db, b.STAFF, [lo1.id], "PS-1", NOW)
    with pytest.raises(BusinessError):
        pack_load_outs(db, b.STAFF, [lo1.id], "PS-2", NOW)
    unpack_load_outs(db, b.STAFF, [lo1.id])
    assert lo1.packing_slip_id is None


def test_save_slip_syncs_date_and_awb_and_unlinks_removed(db):
    c = b.client(db)
    lo1, lo2 = _load_out(db, c), _load_out(db, c)
    h = PackingSlipHeader(reference="PS-1", date=NOW, from_location="Flyway", from_rmp="S2142",
                          to_location="Port", to_rmp="R9", awb_number="AWB1")
    slip = save_packing_slip(db, b.STAFF, h, [lo1.id, lo2.id])
    assert (lo1.date, lo1.awb_number) == (NOW, "AWB1")
    save_packing_slip(db, b.STAFF, h, [lo1.id], slip_id=slip.id)
    assert lo2.packing_slip_id is None
    assert db.query(Supplier).filter_by(rmp_number="R9").count() == 1


def test_save_slip_requires_load_outs_and_unique_reference(db):
    c = b.client(db)
    h = PackingSlipHeader(reference="PS-1", date=NOW, from_location="F", from_rmp="S", to_location="T", to_rmp="R9")
    with pytest.raises(BusinessError):
        save_packing_slip(db, b.STAFF, h, [])
    save_packing_slip(db, b.STAFF, h, [_load_out(db, c).id])
    h.reference = " ps-1"
    with pytest.raises(BusinessError):
        save_packing_slip(db, b.STAFF, h, [_load_out(db, c).id])


def test_complete_and_delete(db):
    c = b.client(db)
    lo = _load_out(db, c)
    slip = pack_load_outs(db, b.STAFF, [lo.id], "PS-1", NOW)
    set_packing_slip_completed(db, b.STAFF, slip.id, True, NOW)
    assert slip.completed == NOW
    with pytest.raises(PermissionDenied):
        delete_packing_slips(db, b.STAFF, [slip.id])
    delete_packing_slips(db, b.ADMIN, [slip.id])
    assert lo.packing_slip_id is None


def test_save_slip_requires_same_to_rmp(db):
    c = b.client(db)
    h = PackingSlipHeader(reference="PS-1", date=NOW, from_location="F", from_rmp="S", to_location="T", to_rmp="R9")
    with pytest.raises(BusinessError) as e:
        save_packing_slip(db, b.STAFF, h, [_load_out(db, c).id, _load_out(db, c, to_rmp="X1").id])
    assert e.value.field == "to_rmp"
    db.rollback()
    c = b.client(db)
    save_packing_slip(db, b.STAFF, h, [_load_out(db, c, to_rmp=" r9 ").id])  # 忽略大小写与首尾空格


def test_historic_mismatched_slip_stays_editable_until_rmp_changes(db):
    c = b.client(db)
    good, odd = _load_out(db, c), _load_out(db, c, to_rmp="S73")
    slip = pack_load_outs(db, b.STAFF, [good.id], "PS-1", NOW)
    odd.packing_slip = slip  # 模拟迁移来的历史数据：旧系统编辑页不校验 RMP
    db.flush()
    h = PackingSlipHeader(reference="PS-1", date=NOW, from_location="F", from_rmp="S", to_location="Port",
                          to_rmp=slip.to_rmp, awb_number="A")
    save_packing_slip(db, b.STAFF, h, [good.id, odd.id], slip_id=slip.id)
    h.to_rmp = "S73"
    with pytest.raises(BusinessError):
        save_packing_slip(db, b.STAFF, h, [good.id, odd.id], slip_id=slip.id)


def test_pack_into_existing_slip_requires_its_to_rmp(db):
    c = b.client(db)
    slip = pack_load_outs(db, b.STAFF, [_load_out(db, c).id], "PS-1", NOW)  # 装箱单 RMP = R9
    other = _load_out(db, c, to_rmp="X1")
    with pytest.raises(BusinessError) as e:
        pack_load_outs(db, b.STAFF, [other.id], " ps-1 ", NOW)
    assert "X1" in e.value.message or "R9" in e.value.message
    assert other.packing_slip_id is None
    same = _load_out(db, c, to_rmp=" r9 ")
    assert pack_load_outs(db, b.STAFF, [same.id], "PS-1", NOW).id == slip.id
