# -*- coding: utf-8 -*-
from decimal import Decimal as D
from unittest import mock

import responses
from django.http import Http404
from django.test import TestCase, override_settings
from django.test.client import RequestFactory

from oscar.core.loading import get_class, get_model
from oscar.test import factories

from ocyan.plugin.oscar.pipeline import (
    PIPELINE_STATUS_NEW,
    PIPELINE_STATUS_PAID,
    PIPELINE_STATUS_REFUND,
)

from .constants import get_mollie_payments
from .mixins import MolliePaymentMixin
from .provider import (
    MollieConfigurationError,
    MollieResponseError,
    format_amount,
    payment_status,
)
from .webhooks import event_fingerprint, notification_reference

Source = get_model("payment", "Source")
SourceType = get_model("payment", "SourceType")
Basket = get_model("basket", "Basket")
Selector = get_class("partner.strategy", "Selector")
Order = get_model("order", "Order")


@override_settings(ALLOWED_HOSTS=["*"])
class MollieTests(TestCase):
    def test_provider_primitives_validate_amounts_and_webhook_references(self):
        self.assertEqual(
            format_amount(D("10"), "eur"), {"currency": "EUR", "value": "10.00"}
        )
        self.assertEqual(notification_reference({"id": "tr_valid"}), "tr_valid")
        self.assertEqual(payment_status({"status": "paid"}), "paid")
        self.assertEqual(
            event_fingerprint({"id": "tr_valid", "status": "paid"}),
            event_fingerprint({"id": "tr_valid", "status": "paid"}),
        )
        with self.assertRaises(MollieConfigurationError):
            format_amount("0", "EUR")
        with self.assertRaises(MollieResponseError):
            notification_reference({"id": "unknown"})

    def create_basket(self, empty=False):
        basket = Basket.objects.create()
        basket.strategy = Selector().strategy()
        if not empty:
            product = factories.create_product()
            factories.create_stockrecord(product, num_in_stock=2)
            basket.add_product(product)
        return basket

    def setUp(self):
        self.factory = RequestFactory()
        self.source_type = SourceType.objects.create(
            code="mollie:ideal", name="Mollie ideal"
        )
        self.order = factories.create_order(
            guest_email="hahaikheblekker@toch-heus-geen.email",
            status="pending-payment",
            basket=self.create_basket(),
        )
        source = Source(
            order=self.order,
            source_type=self.source_type,
            amount_allocated=D("10.00"),
            currency="EUR",
            reference="tr_inggg",
        )
        source.save()

    @responses.activate
    def test_update_payment_status(self):
        responses.add(
            responses.GET,
            "https://api.mollie.com/v2/payments/tr_inggg",
            json={
                "resource": "payment",
                "id": "tr_inggg",
                "mode": "test",
                "createdDatetime": "2018-11-21T14:30:07.0Z",
                "status": "paid",
                "paidAt": "2018-11-21T14:34:50.0Z",
                "amount": {"value": "900.00"},
                "method": "ideal",
                "metadata": {"order_id": "12345"},
            },
            status=200,
            content_type="application/json",
        )

        mollie_payment = MolliePaymentMixin("test_key")
        mollie_payment.update_payment_status("tr_inggg")
        order = Order.objects.get(number="100001")
        self.assertEqual(order.status, "paid")

    @responses.activate
    @mock.patch("ocyan.mollie.mixins.send_order_confirmation_message")
    def test_update_payment_status_banktransfer(
        self, send_order_confirmation_message_mock
    ):
        source_type = SourceType.objects.create(
            code="mollie:banktransfer", name="Mollie banktransfer"
        )
        order = factories.create_order(
            guest_email="hahaikheblekker@toch-heus-geen.email",
            status="pending-payment",
            basket=self.create_basket(),
        )
        source = Source(
            order=order,
            source_type=source_type,
            amount_allocated=D("10.00"),
            currency="EUR",
            reference="tr_raket",
        )
        source.save()

        responses.add(
            responses.GET,
            "https://api.mollie.com/v2/payments/tr_raket",
            json={
                "resource": "payment",
                "id": "tr_raket",
                "mode": "test",
                "createdDatetime": "2018-11-21T14:30:07.0Z",
                "status": "open",
                "amount": {"value": "900.00"},
                "method": "banktransfer",
                "metadata": {"order_id": "12345"},
            },
            status=200,
            content_type="application/json",
        )

        mollie_payment = MolliePaymentMixin("test_key")
        payment = mollie_payment.mollie.payments.get("tr_raket")
        mollie_payment.update_payment_status(payment=payment)
        order.refresh_from_db()
        send_order_confirmation_message_mock.assert_called_once_with(
            "ORDER_PLACED", order
        )
        self.assertEqual(order.status, "pending-payment")

    def test_complete_order(self):
        mollie_payment = MolliePaymentMixin("test_key")
        mollie_payment.complete_order(
            self.order, D("10.00"), "tr_inggg", "paid", "ideal"
        )
        source = self.order.sources.first()
        self.assertEqual(source.reference, "tr_inggg")

    def test_get_source(self):
        mollie_payment = MolliePaymentMixin("test_key")
        source_type = mollie_payment.get_source("banaan")
        self.assertEqual(source_type.code, "mollie:banaan")

    def test_get_order_with_method(self):
        mollie_payment = MolliePaymentMixin("test_key")
        order = mollie_payment.get_order_with_method("tr_inggg", "ideal")
        self.assertEqual(order.number, "100001")

    def test_get_order_with_method_404(self):
        mollie_payment = MolliePaymentMixin("test_key")
        with self.assertRaises(Http404):
            mollie_payment.get_order_with_method("banaan", "ideal")

    def test_get_mollie_payments(self):
        methods = get_mollie_payments("app_code", [])
        self.assertEqual(methods, [])

    @responses.activate
    def test_get_mandate_status(self):
        responses.add(
            responses.GET,
            "https://api.mollie.com/v2/customers/cst_banaan",
            json={
                "resource": "customer",
                "id": "cst_banaan",
                "mode": "test",
                "name": "Temoeri uwkm",
                "email": "superuser@uwkm.nl",
                "locale": None,
                "metadata": None,
                "createdAt": "2019-01-18T13:49:57+00:00",
                "_links": {
                    "self": {
                        "href": "https://api.mollie.com/v2/customers/cst_banaan",
                        "type": "application/hal+json",
                    },
                    "mandates": {
                        "href": "https://api.mollie.com/v2/customers/cst_banaan/mandates",
                        "type": "application/hal+json",
                    },
                    "subscriptions": {
                        "href": "https://api.mollie.com/v2/customers/cst_banaan/subscriptions",
                        "type": "application/hal+json",
                    },
                    "payments": {
                        "href": "https://api.mollie.com/v2/customers/cst_banaan/payments",
                        "type": "application/hal+json",
                    },
                    "documentation": {
                        "href": "https://docs.mollie.com/reference/v2/customers-api/get-customer",
                        "type": "text/html",
                    },
                },
            },
            status=200,
            content_type="application/json",
        )
        responses.add(
            responses.GET,
            "https://api.mollie.com/v2/customers/cst_banaan/mandates",
            json={
                "_embedded": {
                    "mandates": [
                        {
                            "resource": "mandate",
                            "id": "mdt_banaan",
                            "mode": "test",
                            "status": "valid",
                            "method": "creditcard",
                            "customerId": "cst_banaan",
                            "mandateReference": None,
                        }
                    ]
                },
                "count": 1,
            },
            status=200,
            content_type="application/json",
        )

        mollie_payment = MolliePaymentMixin("test_key")
        mandate_status = mollie_payment.get_mandate_status("cst_banaan")
        self.assertEqual(mandate_status, True)

    @responses.activate
    def test_refund(self):
        # force set the order on paid
        order = Order.objects.get(number="100001")
        order.set_status(PIPELINE_STATUS_PAID)

        responses.add(
            responses.GET,
            "https://api.mollie.com/v2/payments/tr_inggg",
            json={
                "resource": "payment",
                "id": "tr_inggg",
                "mode": "test",
                "amount": {"value": "10.00", "currency": "EUR"},
                "method": "ideal",
                "status": "paid",
                "paidAt": "2019-02-08T08:17:54+00:00",
                "amountRefunded": {"value": "10.00", "currency": "EUR"},
                "amountRemaining": {"value": "10.00", "currency": "EUR"},
                "profileId": "pfl_banaan",
                "sequenceType": "oneoff",
                "settlementAmount": {"value": "10.00", "currency": "EUR"},
                "_links": {
                    "refunds": {
                        "href": "https://api.mollie.com/v2/payments/tr_inggg/refunds",
                        "type": "application/hal+json",
                    }
                },
            },
            status=200,
            content_type="application/json",
        )
        responses.add(
            responses.GET,
            "https://api.mollie.com/v2/payments/tr_inggg/refunds",
            json={
                "_embedded": {
                    "refunds": [
                        {
                            "resource": "refund",
                            "id": "re_banaan",
                            "amount": {"value": "10.00", "currency": "EUR"},
                            "status": "refunded",
                            "createdAt": "2019-02-08T09:44:32+00:00",
                            "description": "Order 9",
                            "paymentId": "tr_inggg",
                            "settlementAmount": {"value": "-10.00", "currency": "EUR"},
                            "_links": {
                                "self": {
                                    "href": "https://api.mollie.com/v2/payments/tr_inggg/refunds/re_banaan",
                                    "type": "application/hal+json",
                                },
                                "payment": {
                                    "href": "https://api.mollie.com/v2/payments/tr_inggg",
                                    "type": "application/hal+json",
                                },
                            },
                        }
                    ]
                }
            },
            status=200,
            content_type="application/json",
        )

        mollie_payment = MolliePaymentMixin("test_key")
        mollie_payment.update_payment_status("tr_inggg")
        order = Order.objects.get(number="100001")
        self.assertEqual(order.status, PIPELINE_STATUS_REFUND)

    def test_complete_order_two_times(self):
        """This can happen whenever the callback and webhook are called after an order is paid"""
        order = factories.create_order(
            guest_email="admin@admin.nl",
            status=PIPELINE_STATUS_NEW,
            basket=self.create_basket(),
        )
        source_type = SourceType.objects.create(
            name="klarnapaylater", code="mollie:klarnapaylater"
        )
        order.sources.create(reference="ord_wpk8g5", source_type=source_type)
        MolliePaymentMixin("test_key").complete_order(
            order=order,
            amount=D("10.00"),
            reference="ord_wpk8g5",
            status=PIPELINE_STATUS_PAID,
            method="klarnapaylater",
        )
        order.status = PIPELINE_STATUS_PAID
        order.save()
        MolliePaymentMixin("test_key").complete_order(
            order=order,
            amount=D("10.00"),
            reference="ord_wpk8g5",
            status=PIPELINE_STATUS_PAID,
            method="klarnapaylater",
        )
        order.refresh_from_db()

        self.assertEqual(order.sources.all().count(), 1)
        self.assertEqual(order.sources.first().amount_debited, D("10.00"))
