defmodule Berrypod.Providers.PrintfulTest do
  use Berrypod.DataCase, async: true

  alias Berrypod.Providers.Printful

  describe "provider_type/0" do
    test "returns printful" do
      assert Printful.provider_type() == "printful"
    end
  end

  describe "test_connection/1" do
    test "returns error when no API key" do
      conn = %Berrypod.Products.ProviderConnection{
        provider_type: "printful",
        api_key_encrypted: nil
      }

      assert {:error, :no_api_key} = Printful.test_connection(conn)
    end
  end

  describe "fetch_products/1" do
    test "returns error when no store_id in config" do
      conn = %Berrypod.Products.ProviderConnection{
        provider_type: "printful",
        api_key_encrypted: nil,
        config: %{}
      }

      assert {:error, :no_store_id} = Printful.fetch_products(conn)
    end

    test "returns error when no API key" do
      conn = %Berrypod.Products.ProviderConnection{
        provider_type: "printful",
        api_key_encrypted: nil,
        config: %{"store_id" => "12345"}
      }

      assert {:error, :no_api_key} = Printful.fetch_products(conn)
    end
  end

  describe "submit_order/2" do
    test "returns error when no store_id in config" do
      conn = %Berrypod.Products.ProviderConnection{
        provider_type: "printful",
        api_key_encrypted: nil,
        config: %{}
      }

      assert {:error, :no_store_id} = Printful.submit_order(conn, %{})
    end

    test "returns error when no API key" do
      conn = %Berrypod.Products.ProviderConnection{
        provider_type: "printful",
        api_key_encrypted: nil,
        config: %{"store_id" => "12345"}
      }

      assert {:error, :no_api_key} = Printful.submit_order(conn, %{})
    end
  end

  describe "get_order_status/2" do
    test "returns error when no store_id in config" do
      conn = %Berrypod.Products.ProviderConnection{
        provider_type: "printful",
        api_key_encrypted: nil,
        config: %{}
      }

      assert {:error, :no_store_id} = Printful.get_order_status(conn, "12345")
    end

    test "returns error when no API key" do
      conn = %Berrypod.Products.ProviderConnection{
        provider_type: "printful",
        api_key_encrypted: nil,
        config: %{"store_id" => "12345"}
      }

      assert {:error, :no_api_key} = Printful.get_order_status(conn, "12345")
    end
  end

  describe "fetch_shipping_rates/2" do
    test "returns error when no API key" do
      conn = %Berrypod.Products.ProviderConnection{
        provider_type: "printful",
        api_key_encrypted: nil,
        config: %{"store_id" => "12345"}
      }

      assert {:error, :no_api_key} = Printful.fetch_shipping_rates(conn, [])
    end
  end

  describe "extract_option_types/1" do
    test "extracts color and size options" do
      provider_data = %{
        "options" => [
          %{
            "name" => "Color",
            "type" => "color",
            "values" => [
              %{"title" => "Black"},
              %{"title" => "Natural", "hex" => "#F5F5DC"}
            ]
          },
          %{
            "name" => "Size",
            "type" => "size",
            "values" => [
              %{"title" => "S"},
              %{"title" => "M"},
              %{"title" => "L"}
            ]
          }
        ]
      }

      result = Printful.extract_option_types(provider_data)

      assert length(result) == 2

      [color_opt, size_opt] = result
      assert color_opt.name == "Color"
      assert color_opt.type == :color
      assert length(color_opt.values) == 2
      assert hd(color_opt.values).title == "Black"
      assert Enum.at(color_opt.values, 1).hex == "#F5F5DC"

      assert size_opt.name == "Size"
      assert size_opt.type == :size
      assert length(size_opt.values) == 3
    end

    test "returns empty list for nil provider_data" do
      assert Printful.extract_option_types(nil) == []
    end

    test "returns empty list for provider_data without options" do
      assert Printful.extract_option_types(%{}) == []
    end
  end

  describe "product normalization" do
    test "normalizes sync product response correctly" do
      {sync_product, sync_variants} = printful_sync_product_response()
      normalized = normalize_product(sync_product, sync_variants)

      assert normalized.provider_product_id == "456789"
      assert normalized.title == "PC Man T-Shirt"
      assert normalized.description == ""
      assert normalized.category == "Apparel"

      # Images — one per unique colour from preview files
      assert length(normalized.images) == 2
      [img1, img2] = normalized.images
      assert img1.position == 0
      assert img2.position == 1
      assert img1.alt == "Black"
      assert img2.alt == "Natural"

      # Variants
      assert length(normalized.variants) == 4
      [v1 | _] = normalized.variants
      assert v1.provider_variant_id == "5001"
      assert v1.title == "Black / S"
      assert v1.price == 1350
      assert v1.sku == "PCM-BK-S"
      assert v1.is_enabled == true
      assert v1.is_available == true
      assert v1.options == %{"Color" => "Black", "Size" => "S"}

      # Provider data
      assert normalized.provider_data.catalog_product_id == 71
      assert normalized.provider_data.blueprint_id == 71
      assert normalized.provider_data.print_provider_id == 0
      assert is_list(normalized.provider_data.options)
      assert length(normalized.provider_data.catalog_variant_ids) == 4
    end

    test "handles variant with missing colour or size" do
      sync_product = %{"id" => 1, "name" => "Test", "thumbnail_url" => nil}

      sync_variants = [
        %{
          "id" => 100,
          "color" => nil,
          "size" => "M",
          "retail_price" => "10.00",
          "sku" => "T-M",
          "synced" => true,
          "availability_status" => "active",
          "files" => [],
          "product" => %{"product_id" => 1, "name" => "Test"},
          "variant_id" => 200
        }
      ]

      normalized = normalize_product(sync_product, sync_variants)

      assert length(normalized.variants) == 1
      [v] = normalized.variants
      assert v.title == "M"
      assert v.options == %{"Size" => "M"}
    end

    test "parses price strings correctly" do
      assert parse_price("13.50") == 1350
      assert parse_price("0.99") == 99
      assert parse_price("100.00") == 10000
      assert parse_price(nil) == 0
      assert parse_price(13.5) == 1350
    end
  end

  describe "order status mapping" do
    test "maps Printful statuses to internal statuses" do
      assert map_order_status("draft") == "submitted"
      assert map_order_status("pending") == "submitted"
      assert map_order_status("inprocess") == "processing"
      assert map_order_status("fulfilled") == "shipped"
      assert map_order_status("shipped") == "shipped"
      assert map_order_status("delivered") == "delivered"
      assert map_order_status("canceled") == "cancelled"
      assert map_order_status("failed") == "submitted"
      assert map_order_status("onhold") == "submitted"
      assert map_order_status("unknown_status") == "submitted"
    end
  end

  # =============================================================================
  # Test fixtures — replicate Printful API responses
  # =============================================================================

  defp printful_sync_product_response do
    sync_product = %{
      "id" => 456_789,
      "name" => "PC Man T-Shirt",
      "thumbnail_url" => "https://files.cdn.printful.com/thumb.png",
      "synced" => 4
    }

    sync_variants = [
      %{
        "id" => 5001,
        "color" => "Black",
        "size" => "S",
        "retail_price" => "13.50",
        "sku" => "PCM-BK-S",
        "synced" => true,
        "availability_status" => "active",
        "variant_id" => 4011,
        "product" => %{
          "product_id" => 71,
          "variant_id" => 4011,
          "name" => "Bella+Canvas 3001 Unisex Short Sleeve Jersey T-Shirt"
        },
        "files" => [
          %{
            "type" => "preview",
            "preview_url" => "https://files.cdn.printful.com/preview-black.png",
            "thumbnail_url" => "https://files.cdn.printful.com/thumb-black.png"
          }
        ]
      },
      %{
        "id" => 5002,
        "color" => "Black",
        "size" => "M",
        "retail_price" => "13.50",
        "sku" => "PCM-BK-M",
        "synced" => true,
        "availability_status" => "active",
        "variant_id" => 4012,
        "product" => %{
          "product_id" => 71,
          "variant_id" => 4012,
          "name" => "Bella+Canvas 3001 Unisex Short Sleeve Jersey T-Shirt"
        },
        "files" => [
          %{
            "type" => "preview",
            "preview_url" => "https://files.cdn.printful.com/preview-black.png",
            "thumbnail_url" => "https://files.cdn.printful.com/thumb-black.png"
          }
        ]
      },
      %{
        "id" => 5003,
        "color" => "Natural",
        "size" => "S",
        "retail_price" => "13.50",
        "sku" => "PCM-NT-S",
        "synced" => true,
        "availability_status" => "active",
        "variant_id" => 4013,
        "product" => %{
          "product_id" => 71,
          "variant_id" => 4013,
          "name" => "Bella+Canvas 3001 Unisex Short Sleeve Jersey T-Shirt"
        },
        "files" => [
          %{
            "type" => "preview",
            "preview_url" => "https://files.cdn.printful.com/preview-natural.png",
            "thumbnail_url" => "https://files.cdn.printful.com/thumb-natural.png"
          }
        ]
      },
      %{
        "id" => 5004,
        "color" => "Natural",
        "size" => "M",
        "retail_price" => "13.50",
        "sku" => "PCM-NT-M",
        "synced" => true,
        "availability_status" => "active",
        "variant_id" => 4014,
        "product" => %{
          "product_id" => 71,
          "variant_id" => 4014,
          "name" => "Bella+Canvas 3001 Unisex Short Sleeve Jersey T-Shirt"
        },
        "files" => [
          %{
            "type" => "preview",
            "preview_url" => "https://files.cdn.printful.com/preview-natural.png",
            "thumbnail_url" => "https://files.cdn.printful.com/thumb-natural.png"
          }
        ]
      }
    ]

    {sync_product, sync_variants}
  end

  # =============================================================================
  # Test helpers — replicate private normalization functions
  # =============================================================================

  defp normalize_product(sync_product, sync_variants) do
    images = extract_preview_images(sync_variants)
    catalog_product_id = extract_catalog_product_id(sync_variants)
    catalog_variant_ids = Enum.map(sync_variants, & &1["variant_id"]) |> Enum.reject(&is_nil/1)

    %{
      provider_product_id: to_string(sync_product["id"]),
      title: sync_product["name"],
      description: "",
      category: extract_category(sync_variants),
      images: images,
      variants: Enum.map(sync_variants, &normalize_variant/1),
      provider_data: %{
        catalog_product_id: catalog_product_id,
        catalog_variant_ids: catalog_variant_ids,
        blueprint_id: catalog_product_id,
        print_provider_id: 0,
        thumbnail_url: sync_product["thumbnail_url"],
        options: build_option_types(sync_variants),
        raw: %{sync_product: sync_product}
      }
    }
  end

  defp normalize_variant(sv) do
    %{
      provider_variant_id: to_string(sv["id"]),
      title: build_variant_title(sv),
      sku: sv["sku"],
      price: parse_price(sv["retail_price"]),
      cost: nil,
      options: build_variant_options(sv),
      is_enabled: sv["synced"] == true,
      is_available: sv["availability_status"] == "active"
    }
  end

  defp build_variant_title(sv) do
    [sv["color"], sv["size"]] |> Enum.reject(&is_nil/1) |> Enum.join(" / ")
  end

  defp build_variant_options(sv) do
    opts = %{}
    opts = if sv["color"], do: Map.put(opts, "Color", sv["color"]), else: opts
    if sv["size"], do: Map.put(opts, "Size", sv["size"]), else: opts
  end

  defp extract_preview_images(sync_variants) do
    sync_variants
    |> Enum.flat_map(fn sv ->
      (sv["files"] || [])
      |> Enum.filter(&(&1["type"] == "preview"))
      |> Enum.map(fn file ->
        %{src: file["preview_url"] || file["thumbnail_url"], color: sv["color"]}
      end)
    end)
    |> Enum.uniq_by(& &1.color)
    |> Enum.with_index()
    |> Enum.map(fn {img, index} ->
      %{src: img.src, position: index, alt: img.color}
    end)
  end

  defp extract_catalog_product_id(sync_variants) do
    Enum.find_value(sync_variants, 0, fn sv -> get_in(sv, ["product", "product_id"]) end)
  end

  defp build_option_types(sync_variants) do
    colors =
      sync_variants
      |> Enum.map(& &1["color"])
      |> Enum.reject(&is_nil/1)
      |> Enum.uniq()
      |> Enum.map(fn c -> %{"title" => c} end)

    sizes =
      sync_variants
      |> Enum.map(& &1["size"])
      |> Enum.reject(&is_nil/1)
      |> Enum.uniq()
      |> Enum.map(fn s -> %{"title" => s} end)

    opts = []

    opts =
      if colors != [],
        do: opts ++ [%{"name" => "Color", "type" => "color", "values" => colors}],
        else: opts

    if sizes != [],
      do: opts ++ [%{"name" => "Size", "type" => "size", "values" => sizes}],
      else: opts
  end

  defp extract_category(sync_variants) do
    case sync_variants do
      [sv | _] ->
        product_name = get_in(sv, ["product", "name"]) || ""
        categorize_from_name(product_name)

      [] ->
        nil
    end
  end

  defp categorize_from_name(name) do
    name_lower = String.downcase(name)

    cond do
      has_keyword?(name_lower, ~w[t-shirt tshirt shirt hoodie sweatshirt jogger]) -> "Apparel"
      has_keyword?(name_lower, ~w[canvas poster print frame]) -> "Canvas Prints"
      has_keyword?(name_lower, ~w[mug cup blanket pillow cushion]) -> "Homewares"
      has_keyword?(name_lower, ~w[notebook journal]) -> "Stationery"
      has_keyword?(name_lower, ~w[phone case bag tote hat cap]) -> "Accessories"
      true -> "Apparel"
    end
  end

  defp has_keyword?(text, keywords), do: Enum.any?(keywords, &String.contains?(text, &1))

  defp parse_price(price) when is_binary(price) do
    case Float.parse(price) do
      {float, _} -> round(float * 100)
      :error -> 0
    end
  end

  defp parse_price(price) when is_number(price), do: round(price * 100)
  defp parse_price(_), do: 0

  defp map_order_status("draft"), do: "submitted"
  defp map_order_status("pending"), do: "submitted"
  defp map_order_status("inprocess"), do: "processing"
  defp map_order_status("fulfilled"), do: "shipped"
  defp map_order_status("shipped"), do: "shipped"
  defp map_order_status("delivered"), do: "delivered"
  defp map_order_status("canceled"), do: "cancelled"
  defp map_order_status("failed"), do: "submitted"
  defp map_order_status("onhold"), do: "submitted"
  defp map_order_status(_), do: "submitted"
end
