import os
import sys
import json
import logging

from paypalcheckoutsdk.core import (
    PayPalHttpClient, SandboxEnvironment, LiveEnvironment
)
from paypalcheckoutsdk.orders import OrdersCreateRequest, OrdersCaptureRequest
from paypalhttp.serializers.json_serializer import Json

from django.conf import settings
from django.contrib.sites.models import Site
from django.urls import reverse_lazy

from core import CoffeeGrind

logger = logging.getLogger(__name__)


class PayPalClient:
    def __init__(self):
        self.client_id = settings.PAYPAL_CLIENT_ID
        self.client_secret = settings.PAYPAL_SECRET_ID
        self.site_domain = Site.objects.get_current().domain
        self.site_name = Site.objects.get_current().name

        """Setting up and Returns PayPal SDK environment with PayPal Access credentials.
           For demo purpose, we are using SandboxEnvironment. In production this will be
           LiveEnvironment."""
        if settings.PAYPAL_ENVIRONMENT == "LIVE":
            self.environment = LiveEnvironment(
                client_id=self.client_id, client_secret=self.client_secret
            )
        else:
            self.environment = SandboxEnvironment(
                client_id=self.client_id, client_secret=self.client_secret
            )

        """ Returns PayPal HTTP client instance with environment which has access
            credentials context. This can be used invoke PayPal API's provided the
            credentials have the access to do so. """
        self.client = PayPalHttpClient(self.environment)

    def object_to_json(self, json_data):
        """
        Function to print all json data in an organized readable manner
        """
        result = {}
        if sys.version_info[0] < 3:
            itr = json_data.__dict__.iteritems()
        else:
            itr = json_data.__dict__.items()
        for key, value in itr:
            # Skip internal attributes.
            if key.startswith("__") or key.startswith("_"):
                continue
            result[key] = (
                self.array_to_json_array(value)
                if isinstance(value, list)
                else self.object_to_json(value)
                if not self.is_primittive(value)
                else value
            )
        return result

    def array_to_json_array(self, json_array):
        result = []
        if isinstance(json_array, list):
            for item in json_array:
                result.append(
                    self.object_to_json(item)
                    if not self.is_primittive(item)
                    else self.array_to_json_array(item)
                    if isinstance(item, list)
                    else item
                )
        return result

    def is_primittive(self, data):
        return (
            isinstance(data, str) or isinstance(data, unicode) or isinstance(data, int)
        )


class CreateOrder(PayPalClient):

    """Setting up the JSON request body for creating the Order. The Intent in the
    request body should be set as "CAPTURE" for capture intent flow."""

    def build_request_body(self, params):
        """Method to create body with CAPTURE intent"""
        processed_items = [
            {
                # Shows within upper-right dropdown during payment approval
                "name": f'{item["product"]}: ' + ', '.join([
                    next((
                            f"{value['quantity']} x {v[1]}"
                            for i, v in enumerate(CoffeeGrind.GRIND_CHOICES)
                            if v[0] == key
                        ),
                        None,
                    ) for key, value in item["variations"].items()]
                )[:100],
                # Item details will also be in the completed paypal.com
                # transaction view
                "description": item["product"].subtitle,
                "unit_amount": {
                    "currency_code": settings.DEFAULT_CURRENCY,
                    "value": f'{item["price"]}',
                },
                "quantity": f'{item["quantity"]}',
            }
            for item in params["items"]
        ]

        request_body = {
            "intent": "CAPTURE",
            "application_context": {
                "return_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-done')}",
                "cancel_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-canceled')}",
                "brand_name": f"{self.site_name}",
                # "landing_page": "BILLING",
                # "shipping_preference": "SET_PROVIDED_ADDRESS",
                # "user_action": "CONTINUE"
            },
            "purchase_units": [
                {
                    # "reference_id": "PUHF",
                    "description": "Coffee",
                    # "custom_id": "CUST-HighFashions",
                    # "soft_descriptor": "HighFashions",
                    "amount": {
                        "currency_code": "USD",
                        "value": params["total_price"],
                        "breakdown": {
                            "item_total": {
                                "currency_code": "USD",
                                "value": params["item_total"],
                            },
                            "shipping": {
                                "currency_code": "USD",
                                "value": params["shipping_price"],
                            },
                            "tax_total": {
                                "currency_code": "USD",
                                "value": params["tax_total"],
                            },
                            "discount": {
                                "currency_code": "USD",
                                "value": params["discount"],
                            },
                        },
                    },
                    "items": processed_items,
                    "shipping": {
                        "method": params["shipping_method"],
                        "address": params["shipping_address"],
                    },
                }
            ],
        }

        logger.info(f"\nRequest body: {request_body}\n")

        return request_body

    """ This is the sample function which can be sued to create an order. It uses the
        JSON body returned by buildRequestBody() to create an new Order."""

    def create_order(self, params, debug=False):
        request = OrdersCreateRequest()
        request.headers["prefer"] = "return=representation"
        request.request_body(self.build_request_body(params))
        response = self.client.execute(request)
        if debug:
            logger.debug(
                f"\nStatus Code: {response.status_code}",
            )
            logger.debug(
                f"\nStatus: {response.result.status}",
            )
            logger.debug(
                f"\nOrder ID: {response.result.id}",
            )
            logger.debug(
                f"\nIntent: {response.result.intent}",
            )
            logger.debug(f"\njson_data: {response.result}")

        return response


class CaptureOrder(PayPalClient):
    def capture_order(self, order_id, debug=False):
        """Method to capture order using order_id"""
        request = OrdersCaptureRequest(order_id)
        response = self.client.execute(request)

        data = response.result.__dict__["_dict"]
        data["redirect_urls"] = {
            "return_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-done')}",
            "cancel_url": f"https://{self.site_domain}{reverse_lazy('storefront:payment-canceled')}",
        }

        return data
