import json
from dataclasses import asdict
from decimal import Decimal
from itertools import chain
from unittest import mock
from unittest.mock import ANY

import graphene

from ...core.utils.json_serializer import CustomJsonEncoder
from ...discount import DiscountValueType, OrderDiscountType
from ...order import OrderLineData, OrderOrigin
from ...order.actions import fulfill_order_lines
from ...order.models import Order
from ...plugins.manager import get_plugins_manager
from ...plugins.webhook.utils import from_payment_app_id
from ...product.models import ProductVariant
from ...warehouse import WarehouseClickAndCollectOption
from ..payloads import (
    ORDER_FIELDS,
    PRODUCT_VARIANT_FIELDS,
    generate_checkout_payload,
    generate_customer_payload,
    generate_fulfillment_lines_payload,
    generate_invoice_payload,
    generate_list_gateways_payload,
    generate_order_payload,
    generate_payment_payload,
    generate_product_variant_payload,
    generate_product_variant_with_stock_payload,
    generate_sale_payload,
    generate_translation_payload,
)


@mock.patch("saleor.webhook.payloads.generate_fulfillment_lines_payload")
def test_generate_order_payload(
    mocked_fulfillment_lines, order_with_lines, fulfilled_order, payment_txn_captured
):
    mocked_fulfillment_lines.return_value = "{}"

    payment_txn_captured.psp_reference = "123"
    payment_txn_captured.save(update_fields=["psp_reference"])

    new_order = Order.objects.create(
        channel=order_with_lines.channel,
        billing_address=order_with_lines.billing_address,
    )
    order_with_lines.origin = OrderOrigin.REISSUE
    order_with_lines.original = new_order
    order_with_lines.save(update_fields=["origin", "original"])

    order_with_lines.discounts.create(
        type=OrderDiscountType.MANUAL,
        value_type=DiscountValueType.PERCENTAGE,
        value=Decimal("20"),
        amount_value=Decimal("33.0"),
        reason="Discount from staff",
    )
    order_with_lines.discounts.create(
        type=OrderDiscountType.VOUCHER,
        value_type=DiscountValueType.PERCENTAGE,
        value=Decimal("10"),
        amount_value=Decimal("16.5"),
        name="Voucher",
    )

    line_without_sku = order_with_lines.lines.last()
    line_without_sku.product_sku = None
    line_without_sku.save()

    assert fulfilled_order.fulfillments.count() == 1
    fulfillment = fulfilled_order.fulfillments.first()

    order_id = graphene.Node.to_global_id("Order", order_with_lines.id)
    payload = json.loads(generate_order_payload(order_with_lines))[0]

    assert order_id == payload["id"]
    for field in ORDER_FIELDS:
        assert payload.get(field) is not None

    assert payload.get("shipping_method")
    assert payload.get("shipping_tax_rate")
    assert payload.get("lines")
    assert payload.get("shipping_address")
    assert payload.get("billing_address")
    assert payload.get("fulfillments")
    assert payload.get("discounts")
    assert payload.get("original") == graphene.Node.to_global_id("Order", new_order.pk)
    assert payload.get("payments")
    assert len(payload.get("payments")) == 1
    payments_data = payload.get("payments")[0]
    assert payments_data == {
        "id": graphene.Node.to_global_id("Payment", payment_txn_captured.pk),
        "gateway": payment_txn_captured.gateway,
        "payment_method_type": payment_txn_captured.payment_method_type,
        "cc_brand": payment_txn_captured.cc_brand,
        "is_active": payment_txn_captured.is_active,
        "created": ANY,
        "modified": ANY,
        "charge_status": payment_txn_captured.charge_status,
        "psp_reference": payment_txn_captured.psp_reference,
        "total": str(payment_txn_captured.total),
        "type": "Payment",
        "captured_amount": str(payment_txn_captured.captured_amount),
        "currency": payment_txn_captured.currency,
        "billing_email": payment_txn_captured.billing_email,
        "billing_first_name": payment_txn_captured.billing_first_name,
        "billing_last_name": payment_txn_captured.billing_last_name,
        "billing_company_name": payment_txn_captured.billing_company_name,
        "billing_address_1": payment_txn_captured.billing_address_1,
        "billing_address_2": payment_txn_captured.billing_address_2,
        "billing_city": payment_txn_captured.billing_city,
        "billing_city_area": payment_txn_captured.billing_city_area,
        "billing_postal_code": payment_txn_captured.billing_postal_code,
        "billing_country_code": payment_txn_captured.billing_country_code,
        "billing_country_area": payment_txn_captured.billing_country_area,
    }

    mocked_fulfillment_lines.assert_called_with(fulfillment)


def test_generate_fulfillment_lines_payload(order_with_lines):
    fulfillment = order_with_lines.fulfillments.create(tracking_number="123")
    line = order_with_lines.lines.first()
    stock = line.allocations.get().stock
    warehouse_pk = stock.warehouse.pk
    fulfillment_line = fulfillment.lines.create(
        order_line=line, quantity=line.quantity, stock=stock
    )
    fulfill_order_lines(
        [OrderLineData(line=line, quantity=line.quantity, warehouse_pk=warehouse_pk)],
        get_plugins_manager(),
    )
    payload = json.loads(generate_fulfillment_lines_payload(fulfillment))[0]

    assert payload == {
        "currency": "USD",
        "product_name": line.product_name,
        "variant_name": line.variant_name,
        "product_sku": line.product_sku,
        "product_variant_id": line.product_variant_id,
        "id": graphene.Node.to_global_id("FulfillmentLine", fulfillment_line.id),
        "product_type": "Default Type",
        "quantity": fulfillment_line.quantity,
        "total_price_gross_amount": str(
            line.unit_price.gross.amount * fulfillment_line.quantity
        ),
        "total_price_net_amount": str(
            line.unit_price.net.amount * fulfillment_line.quantity
        ),
        "type": "FulfillmentLine",
        "undiscounted_unit_price_gross": str(line.undiscounted_unit_price.gross.amount),
        "undiscounted_unit_price_net": str(line.undiscounted_unit_price.net.amount),
        "unit_price_gross": str(line.unit_price.gross.amount),
        "unit_price_net": str(line.unit_price.net.amount),
        "weight": 0.0,
        "weight_unit": "gram",
        "warehouse_id": graphene.Node.to_global_id(
            "Warehouse", fulfillment_line.stock.warehouse_id
        ),
    }


def test_generate_fulfillment_lines_payload_deleted_variant(order_with_lines):
    # given
    fulfillment = order_with_lines.fulfillments.create(tracking_number="123")
    line = order_with_lines.lines.first()
    stock = line.allocations.get().stock
    warehouse_pk = stock.warehouse.pk
    fulfillment.lines.create(order_line=line, quantity=line.quantity, stock=stock)
    fulfill_order_lines(
        [OrderLineData(line=line, quantity=line.quantity, warehouse_pk=warehouse_pk)],
        get_plugins_manager(),
    )

    # when
    line.variant.delete()
    payload = json.loads(generate_fulfillment_lines_payload(fulfillment))[0]

    # then
    assert payload["product_type"] is None
    assert payload["weight"] is None


def test_order_lines_have_all_required_fields(order, order_line_with_one_allocation):
    order.lines.add(order_line_with_one_allocation)
    line = order_line_with_one_allocation
    line.unit_discount_amount = Decimal("10.0")
    line.unit_discount_type = DiscountValueType.FIXED
    line.undiscounted_unit_price = line.unit_price + line.unit_discount
    line.undiscounted_total_price = line.undiscounted_unit_price * line.quantity
    line.save()

    payload = json.loads(generate_order_payload(order))[0]
    lines_payload = payload.get("lines")

    assert len(lines_payload) == 1
    line_id = graphene.Node.to_global_id("OrderLine", line.id)
    line_payload = lines_payload[0]
    unit_net_amount = line.unit_price_net_amount.quantize(Decimal("0.001"))
    unit_gross_amount = line.unit_price_gross_amount.quantize(Decimal("0.001"))
    unit_discount_amount = line.unit_discount_amount.quantize(Decimal("0.001"))
    allocation = line.allocations.first()
    undiscounted_unit_price_net_amount = (
        line.undiscounted_unit_price.net.amount.quantize(Decimal("0.001"))
    )
    undiscounted_unit_price_gross_amount = (
        line.undiscounted_unit_price.gross.amount.quantize(Decimal("0.001"))
    )
    undiscounted_total_price_net_amount = (
        line.undiscounted_total_price.net.amount.quantize(Decimal("0.001"))
    )
    undiscounted_total_price_gross_amount = (
        line.undiscounted_total_price.gross.amount.quantize(Decimal("0.001"))
    )

    total_line = line.total_price
    global_warehouse_id = graphene.Node.to_global_id(
        "Warehouse", allocation.stock.warehouse_id
    )
    assert line_payload == {
        "id": line_id,
        "type": "OrderLine",
        "product_name": line.product_name,
        "variant_name": line.variant_name,
        "translated_product_name": line.translated_product_name,
        "translated_variant_name": line.translated_variant_name,
        "product_sku": line.product_sku,
        "product_variant_id": line.product_variant_id,
        "quantity": line.quantity,
        "currency": line.currency,
        "unit_discount_amount": str(unit_discount_amount),
        "unit_discount_type": line.unit_discount_type,
        "unit_discount_reason": line.unit_discount_reason,
        "unit_price_net_amount": str(unit_net_amount),
        "unit_price_gross_amount": str(unit_gross_amount),
        "total_price_net_amount": str(total_line.net.amount.quantize(Decimal("0.001"))),
        "total_price_gross_amount": str(
            total_line.gross.amount.quantize(Decimal("0.001"))
        ),
        "tax_rate": str(line.tax_rate.quantize(Decimal("0.0001"))),
        "allocations": [
            {
                "warehouse_id": global_warehouse_id,
                "quantity_allocated": allocation.quantity_allocated,
            }
        ],
        "undiscounted_unit_price_net_amount": str(undiscounted_unit_price_net_amount),
        "undiscounted_unit_price_gross_amount": str(
            undiscounted_unit_price_gross_amount
        ),
        "undiscounted_total_price_net_amount": str(undiscounted_total_price_net_amount),
        "undiscounted_total_price_gross_amount": str(
            undiscounted_total_price_gross_amount
        ),
    }


def test_order_line_without_sku_still_has_id(order, order_line_with_one_allocation):
    order.lines.add(order_line_with_one_allocation)
    line = order_line_with_one_allocation
    line.unit_discount_amount = Decimal("10.0")
    line.unit_discount_type = DiscountValueType.FIXED
    line.undiscounted_unit_price = line.unit_price + line.unit_discount
    line.undiscounted_total_price = line.undiscounted_unit_price * line.quantity
    line.product_sku = None
    line.save()

    payload = json.loads(generate_order_payload(order))[0]
    lines_payload = payload.get("lines")

    assert len(lines_payload) == 1

    line_payload = lines_payload[0]
    assert line_payload["product_sku"] is None
    assert line_payload["product_variant_id"] == line.product_variant_id


def test_generate_base_product_variant_payload(product_with_two_variants):
    stocks_to_serialize = [
        variant.stocks.first() for variant in product_with_two_variants.variants.all()
    ]
    first_stock, second_stock = stocks_to_serialize
    payload = json.loads(
        generate_product_variant_with_stock_payload(stocks_to_serialize)
    )
    expected_payload = [
        {
            "type": "Stock",
            "id": graphene.Node.to_global_id("Stock", first_stock.id),
            "product_id": graphene.Node.to_global_id(
                "Product", first_stock.product_variant.product_id
            ),
            "product_variant_id": graphene.Node.to_global_id(
                "ProductVariant", first_stock.product_variant_id
            ),
            "warehouse_id": graphene.Node.to_global_id(
                "Warehouse", first_stock.warehouse_id
            ),
            "product_slug": "test-product-with-two-variant",
        },
        {
            "type": "Stock",
            "id": graphene.Node.to_global_id("Stock", second_stock.id),
            "product_id": graphene.Node.to_global_id(
                "Product", second_stock.product_variant.product_id
            ),
            "product_variant_id": graphene.Node.to_global_id(
                "ProductVariant", second_stock.product_variant_id
            ),
            "warehouse_id": graphene.Node.to_global_id(
                "Warehouse", second_stock.warehouse_id
            ),
            "product_slug": "test-product-with-two-variant",
        },
    ]
    assert payload == expected_payload


def test_generate_product_variant_payload(
    product_with_variant_with_two_attributes, product_with_images, channel_USD
):
    variant = product_with_variant_with_two_attributes.variants.first()
    payload = json.loads(generate_product_variant_payload([variant]))[0]
    variant_id = graphene.Node.to_global_id("ProductVariant", variant.id)
    additional_fields = ["channel_listings"]
    extra_dict_data = ["attributes", "product_id", "media"]
    payload_fields = list(
        chain(
            ["id", "type"], PRODUCT_VARIANT_FIELDS, extra_dict_data, additional_fields
        )
    )

    for field in payload_fields:
        assert payload.get(field) is not None

    assert variant_id is not None
    assert payload["id"] == variant_id
    assert payload["sku"] == "prodVar1"
    assert len(payload["attributes"]) == 2
    assert len(payload["channel_listings"]) == 1
    assert payload["channel_listings"][0] == {
        "cost_price_amount": "1.000",
        "currency": "USD",
        "id": ANY,
        "channel_slug": channel_USD.slug,
        "price_amount": "10.000",
        "type": "ProductVariantChannelListing",
    }
    assert len(payload.keys()) == len(payload_fields)


def test_generate_product_variant_with_external_media_payload(
    product_with_variant_with_external_media, channel_USD
):
    variant = product_with_variant_with_external_media.variants.first()
    payload = json.loads(generate_product_variant_payload([variant]))[0]
    variant_id = graphene.Node.to_global_id("ProductVariant", variant.id)
    additional_fields = ["channel_listings"]
    extra_dict_data = ["attributes", "product_id", "media"]
    payload_fields = list(
        chain(
            ["id", "type"], PRODUCT_VARIANT_FIELDS, extra_dict_data, additional_fields
        )
    )
    for field in payload_fields:
        assert payload.get(field) is not None

    assert variant_id is not None
    assert payload["id"] == variant_id
    assert payload["sku"] == "prodVar1"
    assert payload["media"] == [
        {"alt": "video_1", "url": "https://www.youtube.com/watch?v=di8_dJ3Clyo"}
    ]
    assert len(payload["attributes"]) == 2
    assert len(payload["channel_listings"]) == 1
    assert payload["channel_listings"][0] == {
        "cost_price_amount": "1.000",
        "currency": "USD",
        "id": ANY,
        "price_amount": "10.000",
        "channel_slug": channel_USD.slug,
        "type": "ProductVariantChannelListing",
    }
    assert len(payload.keys()) == len(payload_fields)


def test_generate_product_variant_without_sku_payload(
    product_with_variant_with_two_attributes, product_with_images, channel_USD
):
    variant = product_with_variant_with_two_attributes.variants.first()
    variant.sku = None
    variant.save()
    payload = json.loads(generate_product_variant_payload([variant]))[0]
    variant_id = graphene.Node.to_global_id("ProductVariant", variant.id)
    additional_fields = ["channel_listings"]
    extra_dict_data = ["attributes", "product_id", "media"]
    payload_fields = list(
        chain(
            ["id", "type"], PRODUCT_VARIANT_FIELDS, extra_dict_data, additional_fields
        )
    )
    assert variant_id is not None
    assert payload["id"] == variant_id
    assert payload["sku"] is None
    assert len(payload["attributes"]) == 2
    assert len(payload["channel_listings"]) == 1
    assert payload["channel_listings"][0] == {
        "cost_price_amount": "1.000",
        "currency": "USD",
        "id": ANY,
        "channel_slug": channel_USD.slug,
        "price_amount": "10.000",
        "type": "ProductVariantChannelListing",
    }
    assert len(payload.keys()) == len(payload_fields)


def test_generate_product_variant_deleted_payload(
    product_with_variant_with_two_attributes,
):
    variant = product_with_variant_with_two_attributes.variants.prefetch_related(
        "channel_listings",
        "attributes__values",
        "variant_media",
    ).first()
    ProductVariant.objects.filter(id=variant.id).delete()
    payload = json.loads(generate_product_variant_payload([variant]))[0]
    [_, payload_variant_id] = graphene.Node.from_global_id(payload["id"])
    additional_fields = ["channel_listings"]
    extra_dict_data = ["attributes", "product_id", "media"]
    payload_fields = list(
        chain(
            ["id", "type"], PRODUCT_VARIANT_FIELDS, extra_dict_data, additional_fields
        )
    )
    for field in payload_fields:
        assert payload.get(field) is not None

    assert payload_variant_id != "None"
    assert payload["id"] == variant.get_global_id()
    assert payload["sku"] == "prodVar1"
    assert len(payload["attributes"]) == 2
    assert len(payload["channel_listings"]) == 1
    assert len(payload.keys()) == len(payload_fields)


def test_generate_invoice_payload(fulfilled_order):
    fulfilled_order.origin = OrderOrigin.CHECKOUT
    fulfilled_order.save(update_fields=["origin"])
    invoice = fulfilled_order.invoices.first()
    payload = json.loads(generate_invoice_payload(invoice))[0]

    assert payload == {
        "type": "Invoice",
        "id": graphene.Node.to_global_id("Invoice", invoice.id),
        "order": {
            "type": "Order",
            "id": graphene.Node.to_global_id("Order", invoice.order.id),
            "private_metadata": {},
            "metadata": {},
            "created": ANY,
            "status": "fulfilled",
            "origin": OrderOrigin.CHECKOUT,
            "user_email": "test@example.com",
            "shipping_method_name": "DHL",
            "shipping_price_net_amount": "10.000",
            "shipping_price_gross_amount": "12.300",
            "shipping_tax_rate": "0.0000",
            "total_net_amount": "80.000",
            "total_gross_amount": "98.400",
            "weight": "0.0:g",
            "undiscounted_total_net_amount": str(
                fulfilled_order.undiscounted_total_net_amount
            ),
            "undiscounted_total_gross_amount": str(
                fulfilled_order.undiscounted_total_gross_amount
            ),
        },
        "number": "01/12/2020/TEST",
        "created": ANY,
        "external_url": "http://www.example.com/invoice.pdf",
    }


def test_generate_list_gateways_payload(checkout):
    currency = "USD"
    payload = generate_list_gateways_payload(currency, checkout)
    data = json.loads(payload)
    assert data["checkout"] == json.loads(generate_checkout_payload(checkout))[0]
    assert data["currency"] == currency


def test_generate_payment_payload(dummy_webhook_app_payment_data):
    payload = generate_payment_payload(dummy_webhook_app_payment_data)
    expected_payload = asdict(dummy_webhook_app_payment_data)
    expected_payload["payment_method"] = from_payment_app_id(
        dummy_webhook_app_payment_data.gateway
    ).name
    assert payload == json.dumps(expected_payload, cls=CustomJsonEncoder)


def test_generate_checkout_lines_payload(checkout_with_single_item):
    payload = json.loads(generate_checkout_payload(checkout_with_single_item))[0]
    assert payload.get("lines")

    variant = checkout_with_single_item.lines.first().variant
    line = payload["lines"][0]
    assert line["sku"] == variant.sku
    assert line["variant_id"] == variant.get_global_id()


def test_generate_product_translation_payload(product_translation_fr):
    payload = generate_translation_payload(product_translation_fr)
    data = json.loads(payload)
    assert data["id"] == graphene.Node.to_global_id(
        "Product", product_translation_fr.product_id
    )
    assert data["language_code"] == product_translation_fr.language_code
    assert "product_id" not in data.keys()
    assert "product_variant_id" not in data.keys()
    assert "attribute_id" not in data.keys()
    assert "page_id" not in data.keys()

    translation_keys = {i["key"]: i["value"] for i in data["keys"]}
    assert translation_keys["name"] == product_translation_fr.name
    assert translation_keys["description"] == product_translation_fr.description


def test_generate_product_variant_translation_payload(variant_translation_fr):
    payload = generate_translation_payload(variant_translation_fr)
    data = json.loads(payload)
    assert data["id"] == graphene.Node.to_global_id(
        "ProductVariant", variant_translation_fr.product_variant_id
    )
    assert data["language_code"] == variant_translation_fr.language_code
    assert "product_id" not in data.keys()
    assert "product_variant_id" not in data.keys()
    assert "attribute_id" not in data.keys()
    assert "page_id" not in data.keys()

    translation_keys = {i["key"]: i["value"] for i in data["keys"]}
    assert translation_keys["name"] == variant_translation_fr.name


def test_generate_choices_attribute_value_translation_payload(
    translated_attribute_value, color_attribute
):
    payload = generate_translation_payload(translated_attribute_value)
    data = json.loads(payload)
    assert data["id"] == graphene.Node.to_global_id(
        "AttributeValue", translated_attribute_value.attribute_value_id
    )
    assert data["language_code"] == translated_attribute_value.language_code
    assert data["product_id"] is None
    assert data["product_variant_id"] is None
    assert data["attribute_id"] == graphene.Node.to_global_id(
        "Attribute", color_attribute.id
    )
    assert data["page_id"] is None

    translation_keys = {i["key"]: i["value"] for i in data["keys"]}
    assert translation_keys["name"] == translated_attribute_value.name


def test_generate_unique_product_attribute_value_translation_payload(
    translated_product_unique_attribute_value, product, rich_text_attribute
):
    translated_attribute_value = translated_product_unique_attribute_value
    payload = generate_translation_payload(translated_attribute_value)
    data = json.loads(payload)
    assert data["id"] == graphene.Node.to_global_id(
        "AttributeValue", translated_attribute_value.attribute_value_id
    )
    assert data["language_code"] == translated_attribute_value.language_code
    assert data["product_id"] == graphene.Node.to_global_id("Product", product.id)
    assert data["product_variant_id"] is None
    assert data["attribute_id"] == graphene.Node.to_global_id(
        "Attribute", rich_text_attribute.id
    )
    assert data["page_id"] is None
    assert data["page_type_id"] is None
    translation_keys = {i["key"]: i["value"] for i in data["keys"]}
    assert translation_keys["rich_text"] == translated_attribute_value.rich_text


def test_generate_unique_variant_attribute_value_translation_payload(
    translated_variant_unique_attribute_value, variant, rich_text_attribute
):
    translated_attribute_value = translated_variant_unique_attribute_value
    payload = generate_translation_payload(translated_attribute_value)
    data = json.loads(payload)
    assert data["id"] == graphene.Node.to_global_id(
        "AttributeValue", translated_attribute_value.attribute_value_id
    )
    assert data["language_code"] == translated_attribute_value.language_code
    assert data["product_id"] == graphene.Node.to_global_id(
        "Product", variant.product_id
    )
    assert data["product_variant_id"] == graphene.Node.to_global_id(
        "ProductVariant", variant.id
    )
    assert data["attribute_id"] == graphene.Node.to_global_id(
        "Attribute", rich_text_attribute.id
    )
    assert data["page_id"] is None
    assert data["page_type_id"] is None
    translation_keys = {i["key"]: i["value"] for i in data["keys"]}
    assert translation_keys["rich_text"] == translated_attribute_value.rich_text


def test_generate_unique_page_attribute_value_translation_payload(
    translated_page_unique_attribute_value,
    page,
    rich_text_attribute_page_type,
):
    translated_attribute_value = translated_page_unique_attribute_value
    payload = generate_translation_payload(translated_attribute_value)
    data = json.loads(payload)
    assert data["id"] == graphene.Node.to_global_id(
        "AttributeValue", translated_attribute_value.attribute_value_id
    )
    assert data["language_code"] == translated_attribute_value.language_code
    assert data["product_id"] is None
    assert data["product_variant_id"] is None
    assert data["attribute_id"] == graphene.Node.to_global_id(
        "Attribute", rich_text_attribute_page_type.id
    )
    assert data["page_id"] == graphene.Node.to_global_id("Page", page.id)
    assert data["page_type_id"] == graphene.Node.to_global_id(
        "PageType", page.page_type_id
    )
    translation_keys = {i["key"]: i["value"] for i in data["keys"]}
    assert translation_keys["rich_text"] == translated_attribute_value.rich_text


def test_generate_customer_payload(customer_user, address_other_country, address):

    customer = customer_user
    customer.default_billing_address = address_other_country
    customer.save()
    payload = json.loads(generate_customer_payload(customer))[0]
    expected_payload = {
        "type": "User",
        "id": graphene.Node.to_global_id("User", customer.id),
        "default_shipping_address": {
            "type": "Address",
            "id": graphene.Node.to_global_id(
                "Address", customer.default_shipping_address_id
            ),
            "first_name": customer.default_shipping_address.first_name,
            "last_name": customer.default_shipping_address.last_name,
            "company_name": customer.default_shipping_address.company_name,
            "street_address_1": customer.default_shipping_address.street_address_1,
            "street_address_2": customer.default_shipping_address.street_address_2,
            "city": customer.default_shipping_address.city,
            "city_area": customer.default_shipping_address.city_area,
            "postal_code": customer.default_shipping_address.postal_code,
            "country": customer.default_shipping_address.country,
            "country_area": customer.default_shipping_address.country_area,
            "phone": customer.default_shipping_address.phone,
        },
        "default_billing_address": {
            "type": "Address",
            "id": graphene.Node.to_global_id(
                "Address", customer.default_billing_address_id
            ),
            "first_name": customer.default_billing_address.first_name,
            "last_name": customer.default_billing_address.last_name,
            "company_name": customer.default_billing_address.company_name,
            "street_address_1": customer.default_billing_address.street_address_1,
            "street_address_2": customer.default_billing_address.street_address_2,
            "city": customer.default_billing_address.city,
            "city_area": customer.default_billing_address.city_area,
            "postal_code": customer.default_billing_address.postal_code,
            "country": customer.default_billing_address.country,
            "country_area": customer.default_billing_address.country_area,
            "phone": customer.default_billing_address.phone,
        },
        "addresses": [
            {
                "type": "Address",
                "id": graphene.Node.to_global_id(
                    "Address", customer.default_shipping_address_id
                ),
                "first_name": customer.default_shipping_address.first_name,
                "last_name": customer.default_shipping_address.last_name,
                "company_name": customer.default_shipping_address.company_name,
                "street_address_1": customer.default_shipping_address.street_address_1,
                "street_address_2": customer.default_shipping_address.street_address_2,
                "city": customer.default_shipping_address.city,
                "city_area": customer.default_shipping_address.city_area,
                "postal_code": customer.default_shipping_address.postal_code,
                "country": customer.default_shipping_address.country,
                "country_area": customer.default_shipping_address.country_area,
                "phone": customer.default_shipping_address.phone,
            }
        ],
        "private_metadata": customer.private_metadata,
        "metadata": customer.metadata,
        "email": customer.email,
        "first_name": customer.first_name,
        "last_name": customer.last_name,
        "is_active": customer.is_active,
        "date_joined": ANY,
    }

    assert payload == expected_payload


def test_generate_collection_point_payload(order_with_lines_for_cc):
    payload = json.loads(generate_order_payload(order_with_lines_for_cc))[0]

    payload_collection_point = payload.get("collection_point")

    assert payload_collection_point
    assert payload_collection_point.get("address")
    assert payload_collection_point.get("email") == "local@example.com"
    assert payload_collection_point.get("name") == "Local Warehouse"
    assert not payload_collection_point.get("is_private")
    assert (
        payload_collection_point.get("click_and_collect_option")
        == WarehouseClickAndCollectOption.LOCAL_STOCK
    )


def test_generate_sale_payload_no_previous_and_current_has_empty_catalogue_lists(sale):
    payload = json.loads(generate_sale_payload(sale))[0]

    assert not payload["categories_added"]
    assert not payload["categories_removed"]
    assert not payload["collections_added"]
    assert not payload["collections_removed"]
    assert not payload["products_added"]
    assert not payload["products_removed"]

    assert graphene.Node.to_global_id("Sale", sale.id) == payload["id"]


def test_generate_sale_payload_with_current_only_has_empty_removed_fields(sale):
    catalogue_info = {
        "categories": {1, 2, 3},
        "collections": {45, 70, 90},
        "products": {4, 5, 6},
        "variants": {"aa", "bb", "cc"},
    }
    payload = json.loads(generate_sale_payload(sale, current_catalogue=catalogue_info))[
        0
    ]

    assert set(payload["categories_added"]) == catalogue_info["categories"]
    assert set(payload["collections_added"]) == catalogue_info["collections"]
    assert set(payload["products_added"]) == catalogue_info["products"]
    assert set(payload["variants_added"]) == catalogue_info["variants"]
    assert not payload["categories_removed"]
    assert not payload["collections_removed"]
    assert not payload["products_removed"]
    assert not payload["variants_removed"]


def test_generate_sale_payload_with_current_only_has_empty_added_fields(sale):
    catalogue_info = {
        "categories": {1, 2, 3},
        "collections": {45, 70, 90},
        "products": {4, 5, 6},
        "variants": {"aa", "bb", "cc"},
    }
    payload = json.loads(
        generate_sale_payload(sale, previous_catalogue=catalogue_info)
    )[0]

    assert set(payload["categories_removed"]) == catalogue_info["categories"]
    assert set(payload["collections_removed"]) == catalogue_info["collections"]
    assert set(payload["products_removed"]) == catalogue_info["products"]
    assert set(payload["variants_removed"]) == catalogue_info["variants"]
    assert not payload["categories_added"]
    assert not payload["collections_added"]
    assert not payload["products_added"]
    assert not payload["variants_added"]


def test_genereate_sale_payload_calculates_set_differences(sale):
    previous_info = {
        "categories": {1, 2, 3},
        "collections": {45, 70, 90},
        "products": {4, 5, 6},
        "variants": {"aaa", "bbb", "ccc"},
    }
    current_info = {
        "categories": {4, 2, 3},
        "collections": set(),
        "products": {4, 5, 6, 10, 20},
        "variants": {"aaa", "bbb", "ddd"},
    }

    payload = json.loads(
        generate_sale_payload(
            sale, previous_catalogue=previous_info, current_catalogue=current_info
        )
    )[0]

    assert set(payload["categories_removed"]) == {1}
    assert set(payload["categories_added"]) == {4}
    assert set(payload["collections_removed"]) == {45, 70, 90}
    assert not payload["collections_added"]
    assert not payload["products_removed"]
    assert set(payload["products_added"]) == {10, 20}
    assert set(payload["variants_added"]) == {"ddd"}
    assert set(payload["variants_removed"]) == {"ccc"}
