import numpy as np
import pytest

from delivery.eval import ReplaySampler, replay
from delivery.instances import Instance, Order, Van, generate_instance
from delivery.integration import plan
from conftest import line_tm


# ----------------------------------------------------------------------------- normal
@pytest.mark.parametrize("seed", range(3))
def test_plan_serves_every_order_once_on_time(tm, seed):
    inst = generate_instance(20, seed, n_zones=tm.Z)
    res = plan(inst, tm)
    assert res.status == "ok", res.message
    served = sorted(i for r in res.routes.values() for i in r.sequence)
    assert served == sorted(o.id for o in inst.orders)
    for v in inst.vans:
        r = res.routes[v.id]
        assert r.feasible and r.n_late == 0
        assert sum(inst.order(i).demand for i in r.sequence) <= v.capacity
        assert len(r.sequence) <= v.max_stops


def test_replay_counts_late_deliveries_with_known_durations():
    tm = line_tm()
    inst = Instance([Order(0, 2, 480, 495, 1, 5)], [Van(0, 5, 480, 1000)], 0, 1)
    res = plan(inst, tm)
    assert res.status == "ok"
    import pandas as pd
    slow = pd.DataFrame({"pu": [0] * 5, "do": [2] * 5, "hour": [8] * 5, "duration_min": [20.0] * 5})
    fast = slow.assign(duration_min=5.0)
    assert replay(inst, res, ReplaySampler(slow, factor=1.0), tm, n_reps=3)["late_mean"] == 1.0
    assert replay(inst, res, ReplaySampler(fast, factor=1.0), tm, n_reps=3)["late_mean"] == 0.0


# ----------------------------------------------------------------------------- edge
def test_edge_more_vans_than_orders():
    tm = line_tm()
    inst = Instance([Order(0, 3, 480, 700, 1, 5)],
                    [Van(0, 5, 480, 1000), Van(1, 5, 480, 1000), Van(2, 5, 480, 1000)], 0, 1)
    res = plan(inst, tm)
    assert res.status == "ok"
    assert sum(len(r.sequence) for r in res.routes.values()) == 1


# ----------------------------------------------------------------------------- impossible
def test_impossible_unreachable_window_reports_reason_and_widening(capsys):
    tm = line_tm()
    vans = [Van(0, 5, 480, 1000), Van(1, 5, 540, 1000), Van(2, 5, 600, 1000)]
    orders = [Order(0, 1, 480, 600, 1, 5), Order(1, 4, 480, 490, 1, 5)]   # 0 -> 4 takes 21 min
    res = plan(Instance(orders, vans, 0, 1), tm)
    print(res.message)
    out = capsys.readouterr().out
    assert res.status == "no_solution"
    assert "NO SOLUTION" in out and "order 1" in out and "not reachable" in out
    assert res.widening["found"] and res.widening["minutes"] == 15


def test_impossible_single_van_loop_ends_in_no_solution():
    tm = line_tm()
    # every PAIR fits in one van (no "nn" edge), but all three do not: the route is late,
    # the loop learns the first late pair as a different-van rule, and with one van the CSP
    # then has no solution
    orders = [Order(0, 4, 480, 510, 1, 5), Order(1, 2, 480, 500, 1, 5), Order(2, 0, 480, 530, 1, 5)]
    res = plan(Instance(orders, [Van(0, 10, 480, 1000)], 0, 1), tm, suggest=False)
    assert res.loops[0]["csp_status"] == "solved" and res.loops[0]["n_edges_nn"] == 0
    assert res.loops[0]["new_rules"], "late route must produce a learned rule"
    assert res.status == "no_solution" and len(res.loops) == 2
    assert res.loops[1]["n_edges_learned"] == 1
    assert "NO SOLUTION" in res.message
