defmodule Berrypod.ShippingTest do
  use Berrypod.DataCase, async: false

  alias Berrypod.Shipping
  alias Berrypod.Shipping.ShippingRate

  import Berrypod.ProductsFixtures

  describe "upsert_rates/2" do
    test "inserts new rates" do
      conn = provider_connection_fixture()

      rates = [
        %{
          blueprint_id: 100,
          print_provider_id: 200,
          country_code: "GB",
          first_item_cost: 450,
          additional_item_cost: 100,
          currency: "GBP"
        },
        %{
          blueprint_id: 100,
          print_provider_id: 200,
          country_code: "US",
          first_item_cost: 650,
          additional_item_cost: 150,
          currency: "USD"
        }
      ]

      assert {:ok, 2} = Shipping.upsert_rates(conn.id, rates)
      assert Repo.aggregate(ShippingRate, :count) == 2
    end

    test "replaces existing rates on conflict" do
      conn = provider_connection_fixture()

      rates = [
        %{
          blueprint_id: 100,
          print_provider_id: 200,
          country_code: "GB",
          first_item_cost: 450,
          additional_item_cost: 100,
          currency: "GBP"
        }
      ]

      {:ok, 1} = Shipping.upsert_rates(conn.id, rates)

      updated_rates = [
        %{
          blueprint_id: 100,
          print_provider_id: 200,
          country_code: "GB",
          first_item_cost: 550,
          additional_item_cost: 200,
          currency: "GBP"
        }
      ]

      {:ok, 1} = Shipping.upsert_rates(conn.id, updated_rates)

      assert Repo.aggregate(ShippingRate, :count) == 1
      rate = Repo.one(ShippingRate)
      assert rate.first_item_cost == 550
      assert rate.additional_item_cost == 200
    end
  end

  describe "get_rate/3" do
    test "returns rate when it exists" do
      conn = provider_connection_fixture()

      {:ok, _} =
        Shipping.upsert_rates(conn.id, [
          %{
            blueprint_id: 100,
            print_provider_id: 200,
            country_code: "GB",
            first_item_cost: 450,
            additional_item_cost: 100,
            currency: "GBP"
          }
        ])

      rate = Shipping.get_rate(100, 200, "GB")
      assert rate.first_item_cost == 450
      assert rate.country_code == "GB"
    end

    test "returns nil when no rate exists" do
      assert Shipping.get_rate(999, 999, "XX") == nil
    end
  end

  describe "calculate_for_cart/2" do
    test "returns {:ok, 0} for empty cart" do
      assert {:ok, 0} = Shipping.calculate_for_cart([], "GB")
    end

    test "calculates shipping for single item" do
      conn = provider_connection_fixture()

      product =
        product_fixture(%{
          provider_connection: conn,
          provider_data: %{"blueprint_id" => 100, "print_provider_id" => 200}
        })

      variant = product_variant_fixture(%{product: product})

      {:ok, _} =
        Shipping.upsert_rates(conn.id, [
          %{
            blueprint_id: 100,
            print_provider_id: 200,
            country_code: "GB",
            first_item_cost: 450,
            additional_item_cost: 100,
            currency: "GBP"
          }
        ])

      cart_items = [%{variant_id: variant.id, quantity: 1}]
      assert {:ok, 450} = Shipping.calculate_for_cart(cart_items, "GB")
    end

    test "calculates shipping for multiple quantities" do
      conn = provider_connection_fixture()

      product =
        product_fixture(%{
          provider_connection: conn,
          provider_data: %{"blueprint_id" => 100, "print_provider_id" => 200}
        })

      variant = product_variant_fixture(%{product: product})

      {:ok, _} =
        Shipping.upsert_rates(conn.id, [
          %{
            blueprint_id: 100,
            print_provider_id: 200,
            country_code: "GB",
            first_item_cost: 450,
            additional_item_cost: 100,
            currency: "GBP"
          }
        ])

      cart_items = [%{variant_id: variant.id, quantity: 3}]
      # first_item_cost + 2 * additional_item_cost = 450 + 200 = 650
      assert {:ok, 650} = Shipping.calculate_for_cart(cart_items, "GB")
    end

    test "returns error when no rates found" do
      conn = provider_connection_fixture()

      product =
        product_fixture(%{
          provider_connection: conn,
          provider_data: %{"blueprint_id" => 100, "print_provider_id" => 200}
        })

      variant = product_variant_fixture(%{product: product})

      cart_items = [%{variant_id: variant.id, quantity: 1}]
      assert {:error, :rates_not_found} = Shipping.calculate_for_cart(cart_items, "XX")
    end

    test "groups items by print provider" do
      conn = provider_connection_fixture()

      product1 =
        product_fixture(%{
          provider_connection: conn,
          provider_data: %{"blueprint_id" => 100, "print_provider_id" => 200}
        })

      product2 =
        product_fixture(%{
          provider_connection: conn,
          provider_data: %{"blueprint_id" => 101, "print_provider_id" => 200}
        })

      variant1 = product_variant_fixture(%{product: product1})
      variant2 = product_variant_fixture(%{product: product2})

      {:ok, _} =
        Shipping.upsert_rates(conn.id, [
          %{
            blueprint_id: 100,
            print_provider_id: 200,
            country_code: "GB",
            first_item_cost: 450,
            additional_item_cost: 100,
            currency: "GBP"
          },
          %{
            blueprint_id: 101,
            print_provider_id: 200,
            country_code: "GB",
            first_item_cost: 500,
            additional_item_cost: 150,
            currency: "GBP"
          }
        ])

      cart_items = [
        %{variant_id: variant1.id, quantity: 1},
        %{variant_id: variant2.id, quantity: 1}
      ]

      # Same provider: highest first_item_cost (500) + 1 additional item cost
      assert {:ok, cost} = Shipping.calculate_for_cart(cart_items, "GB")
      # 500 (highest first item) + one additional item cost (100 or 150)
      assert cost in [600, 650]
    end
  end

  describe "upsert_rates/3 with exchange rates" do
    test "converts rates to GBP at sync time with buffer" do
      conn = provider_connection_fixture()
      exchange_rates = %{"USD" => 0.80}

      rates = [
        %{
          blueprint_id: 100,
          print_provider_id: 1,
          country_code: "US",
          first_item_cost: 1000,
          additional_item_cost: 500,
          currency: "USD"
        }
      ]

      {:ok, 1} = Shipping.upsert_rates(conn.id, rates, exchange_rates)

      rate = Shipping.get_rate(100, 1, "US")
      assert rate.currency == "GBP"
      # 1000 USD cents * 0.80 rate * 1.05 buffer, ceil'd
      assert rate.first_item_cost == 841
      assert rate.additional_item_cost == 421
    end

    test "leaves GBP rates unconverted" do
      conn = provider_connection_fixture()
      exchange_rates = %{"USD" => 0.80}

      rates = [
        %{
          blueprint_id: 100,
          print_provider_id: 1,
          country_code: "GB",
          first_item_cost: 500,
          additional_item_cost: 200,
          currency: "GBP"
        }
      ]

      {:ok, 1} = Shipping.upsert_rates(conn.id, rates, exchange_rates)

      rate = Shipping.get_rate(100, 1, "GB")
      assert rate.currency == "GBP"
      assert rate.first_item_cost == 500
      assert rate.additional_item_cost == 200
    end
  end

  describe "list_available_countries/0" do
    test "returns distinct country codes" do
      conn = provider_connection_fixture()

      {:ok, _} =
        Shipping.upsert_rates(conn.id, [
          %{
            blueprint_id: 100,
            print_provider_id: 200,
            country_code: "GB",
            first_item_cost: 450,
            additional_item_cost: 100,
            currency: "GBP"
          },
          %{
            blueprint_id: 100,
            print_provider_id: 200,
            country_code: "US",
            first_item_cost: 650,
            additional_item_cost: 150,
            currency: "USD"
          },
          %{
            blueprint_id: 101,
            print_provider_id: 200,
            country_code: "GB",
            first_item_cost: 500,
            additional_item_cost: 100,
            currency: "GBP"
          }
        ])

      countries = Shipping.list_available_countries()
      assert "GB" in countries
      assert "US" in countries
      assert length(countries) == 2
    end
  end
end
