from app.services.product_costing_service import (
    ProductCostAssumptions,
    ProductCostInputItem,
    calculate_product_cost_item,
)


def assumptions() -> ProductCostAssumptions:
    return ProductCostAssumptions(
        grading_per_kg=0.05,
        cracking_per_kg=0.03,
        process_costs={
            "Bagging + Grading": 0.04,
            "Standard Bagging": 0.02,
            "PHF Horse Mixes": 0.01,
            "Peckish": 0.08,
            "Hay & Straw": 0.09,
        },
        client_margins={
            "Specialty": {"distributor_margin": 0.2, "wholesale_margin": 0.1},
            "Peckish": {"distributor_margin": 0.25, "wholesale_margin": 0.15},
            "Hay & Straw": {"distributor_margin": 0.3, "wholesale_margin": 0.2},
            "Straight Grain": {"distributor_margin": 0.1, "wholesale_margin": 0.08},
            "PHF Horse Mixes": {"distributor_margin": 0.18, "wholesale_margin": 0.12},
        },
        bag_costs={
            "20kg_bag": 0.6,
            "bulka_bag": 22.0,
            "own_bag_credit": 0.2,
            "1_5kg_bagging": 0.35,
            "peckish_bag": 0.4,
        },
        freight_costs={
            "freight_per_pallet": 80.0,
            "peckish_freight_per_pallet": 96.0,
            "hay_straw_freight_per_pallet": 120.0,
        },
    )


def item(**overrides) -> ProductCostInputItem:
    values = {
        "client_category": "Specialty",
        "product_name": "Pigeon Mix 20kg",
        "mix_product_name": "Pigeon Mix",
        "unit_type": "Standard",
        "own_bag": None,
        "unit_kg": 20.0,
        "items_per_pallet": 40,
        "bagging_process": "Bagging + Grading",
        "manual_distributor_margin": None,
        "manual_wholesale_margin": None,
    }
    values.update(overrides)
    return ProductCostInputItem(**values)


def test_standard_product_uses_per_kg_components_unit_bag_freight_and_default_margins():
    result = calculate_product_cost_item(item(), assumptions(), 0.5)

    assert result.grading_cost_per_kg == 0.05
    assert result.bagging_cost_per_kg == 0.04
    assert result.bag_cost_per_unit == 0.6
    assert result.freight_cost_per_unit == 2.0
    assert result.finished_product_delivered_cost == 14.4
    assert result.distributor_price == 18.0
    assert result.wholesale_price == 16.0
    assert result.warnings == []


def test_bulka_uses_per_kg_delivered_formula_and_bulka_bag_and_freight_divided_by_unit_kg():
    result = calculate_product_cost_item(item(unit_type="Bulka", unit_kg=1000, items_per_pallet=1), assumptions(), 0.5)

    assert result.bag_cost_per_unit == 0.022
    assert result.freight_cost_per_unit == 0.08
    assert result.finished_product_delivered_cost == 0.692


def test_per_unit_uses_per_kg_delivered_formula_but_standard_pallet_freight():
    result = calculate_product_cost_item(item(unit_type="Per Unit", unit_kg=1, items_per_pallet=10), assumptions(), 0.5)

    assert result.freight_cost_per_unit == 8.0
    assert result.finished_product_delivered_cost == 8.59


def test_peckish_uses_peckish_bag_freight_and_zero_grading():
    result = calculate_product_cost_item(
        item(client_category="Peckish", bagging_process="Peckish", items_per_pallet=24),
        assumptions(),
        0.5,
    )

    assert result.grading_cost_per_kg == 0
    assert result.bagging_cost_per_kg == 0.08
    assert result.bag_cost_per_unit == 0.4
    assert result.freight_cost_per_unit == 4.0


def test_hay_and_straw_uses_hay_freight_and_zero_grading():
    result = calculate_product_cost_item(
        item(client_category="Hay & Straw", bagging_process="Hay & Straw", items_per_pallet=30),
        assumptions(),
        0.5,
    )

    assert result.grading_cost_per_kg == 0
    assert result.freight_cost_per_unit == 4.0


def test_phf_horse_mixes_have_zero_grading():
    result = calculate_product_cost_item(
        item(client_category="PHF Horse Mixes", bagging_process="PHF Horse Mixes"),
        assumptions(),
        0.5,
    )

    assert result.grading_cost_per_kg == 0


def test_own_bag_subtracts_credit_and_no_bag_sets_bag_cost_to_zero():
    own_bag = calculate_product_cost_item(item(own_bag="Yes"), assumptions(), 0.5)
    no_bag = calculate_product_cost_item(item(own_bag="No Bag"), assumptions(), 0.5)

    assert own_bag.bag_cost_per_unit == 0.4
    assert no_bag.bag_cost_per_unit == 0


def test_one_point_five_kg_branch_multiplies_pack_formula_by_eight():
    result = calculate_product_cost_item(item(unit_type="1.5 kg", unit_kg=1.5), assumptions(), 0.5)

    assert result.bag_cost_per_unit == 0.35
    assert result.finished_product_delivered_cost == 10.84


def test_cracked_product_adds_cracking_cost_and_manual_margins_override_defaults():
    result = calculate_product_cost_item(
        item(product_name="Cracked Maize 20kg", manual_distributor_margin=0.1, manual_wholesale_margin=0.05),
        assumptions(),
        0.5,
    )

    assert result.cracking_cost_per_kg == 0.03
    assert result.distributor_price == 16.6667
    assert result.wholesale_price == 15.8


def test_straight_grain_bulka_wholesale_rounds_up_to_two_decimals():
    result = calculate_product_cost_item(
        item(client_category="Straight Grain", unit_type="Bulka", unit_kg=1000, items_per_pallet=1),
        assumptions(),
        0.5,
    )

    assert result.wholesale_price == 0.76


def test_missing_lookup_and_invalid_inputs_generate_warnings_without_prices():
    result = calculate_product_cost_item(
        item(unit_kg=None, items_per_pallet=0, manual_distributor_margin=1.2),
        assumptions(),
        None,
    )

    assert "Missing mix/product cost lookup" in result.warnings
    assert "Missing unit kg" in result.warnings
    assert "Missing pallet quantity" in result.warnings
    assert "Invalid distributor margin" in result.warnings
    assert result.finished_product_delivered_cost is None
    assert result.distributor_price is None
