import requests
from django.utils.decorators import decorator_from_middleware_with_args, method_decorator
from rest_framework import status
from rest_framework.views import APIView
from django.shortcuts import redirect
from authenticate.middleware import AuthorizatorMiddleware
from payment.models import Transaction, UserBalanceHistory, BalancePurchase
from payment.serializers import PaymentCreateInputSerializer, PaymentVerifyInputSerializer, TransactionListInputSerializer, TransactionModelSerializer, TransactionGetInputSerializer
from utils.myresponse import MyResponse
from utils.utils import logs_adder
from wikiazma import settings
from wikiazma.middleware import SerializerValidationMiddleware

serializer_validation_middleware = decorator_from_middleware_with_args(
    SerializerValidationMiddleware)
user_token_or_server_token_or_server_API_key_validation_and_authorization = decorator_from_middleware_with_args(
    AuthorizatorMiddleware)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(PaymentCreateInputSerializer), name='post')
class PaymentCreate(APIView):
    def post(self, request):
        amount = request.middleware_serializer_data.get('amount')
        meta = request.middleware_serializer_data.get('meta')
        user = request.middleware_user

        callback_url = request.build_absolute_uri().replace(
            "/payment/request", "/payment/verify")
        payload = {
            'merchant_id': settings.ZARINPAL_MERCHANT,
            # TODO: change zarinpal currency to TOMAN
            'amount': int(amount * 10),  # Rials
            # TODO
            'description': f'شارژ حساب ویکی‌آزما\n{user.full_name}',
            'callback_url': callback_url,
            'metadata': {
                "full_name": user.full_name
            },
        }

        if user.phone is not None:
            payload['metadata']['mobile'] = user.phone

        elif user.email is not None:
            payload['metadata']['email'] = user.email

        try:

            # zarinpla request
            zarinpal_response = requests.post(
                settings.ZARINPAL_REQUEST_URL, json=payload).json()

            if len(zarinpal_response['errors']) == 0 and zarinpal_response['data']['code'] == 100:

                Transaction.objects.create(
                    amount=amount, user=user, authority=zarinpal_response['data']['authority'], meta=meta)
                url = settings.ZARINPAL_STARTPAY_URL + \
                    zarinpal_response['data']['authority']
                return MyResponse(request,
                                  {'status': 'ok', 'message': 'successful',
                                   'data': {'url': url}},
                                  status=status.HTTP_200_OK)
            else:
                return MyResponse(request,
                                  {'status': 'error', 'message': 'Error code: ' +
                                   str(zarinpal_response['data']['code'])},
                                  status=status.HTTP_500_INTERNAL_SERVER_ERROR)

        except:
            logs_adder(f'CRITICAL: Invalid Zarinpal Response')
            return MyResponse(request, {'status': 'error', 'message': 'Invalid Zarinpal Response'},
                              status=status.HTTP_500_INTERNAL_SERVER_ERROR)


@method_decorator(serializer_validation_middleware(PaymentVerifyInputSerializer), name='get')
class PaymentVerify(APIView):
    def get(self, request):
        authority = request.middleware_serializer_data.get('Authority')
        zarinpal_status = request.middleware_serializer_data.get('Status')

        # find transaction
        try:

            transaction = Transaction.objects.get(
                authority=authority)

        except:
            return redirect(settings.FAILED_PAYMENT_CALLBACK_URL.replace(":authority", authority))

        # applied transaction
        if transaction.ref_id:
            if transaction.status not in [100, 101]:
                logs_adder(
                    f'CRITICAL: transaction with {transaction.ref_id} ref_id has invalid {transaction.status} status')

            return redirect(settings.REPETITIOUS_PAYMENT_CALLBACK_URL.replace(":authority", authority))

        # request to zarinpal for validation
        if zarinpal_status == 'OK':
            payload = {
                'merchant_id': settings.ZARINPAL_MERCHANT,

                # TODO: change zarinpal currency to TOMAN
                'amount': int(transaction.amount * 10),  # Rials
                'authority': authority,
            }
            # if True:
            try:
                zarinpal_response = requests.post(
                    settings.ZARINPAL_VERIFY_URL, json=payload).json()

                print("zarinpal_response", zarinpal_response)

                transaction.status = zarinpal_response['data']['code']

                if zarinpal_response['data']['code'] in [100, 101]:
                    last_user_balance_history = UserBalanceHistory.get_last_user_balance(
                        transaction.user)
                    new_balance_history = UserBalanceHistory(user=transaction.user, amount=transaction.amount,
                                                             reason_meta_data={}, reason=BalancePurchase)
                    new_balance_history.remaining_balance = last_user_balance_history.remaining_balance + \
                        new_balance_history.amount
                    new_balance_history.save()

                    transaction.ref_id = zarinpal_response['data']['ref_id']
                    transaction.save()
                    return redirect(settings.SUCCESS_PAYMENT_CALLBACK_URL.replace(":authority", authority))

                elif len(zarinpal_response['errors']) != 0:
                    transaction.save()
                    return redirect(settings.FAILED_PAYMENT_CALLBACK_URL.replace(":authority", authority))

            except:
                logs_adder(f'CRITICAL: Invalid Zarinpal Response')
                return redirect(settings.FAILED_PAYMENT_CALLBACK_URL.replace(":authority", authority))

        # canceled transaction
        else:
            # don't use zarinpal_status. User may tamper with the input.
            transaction.status = 'NOK'
            transaction.save()

            return redirect(settings.FAILED_PAYMENT_CALLBACK_URL.replace(":authority", authority))


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(TransactionGetInputSerializer), name='post')
class TransactionGet(APIView):
    def post(self, request):

        authority = request.middleware_serializer_data.get('authority')
        caller = request.middleware_user

        try:

            transaction_queryset = Transaction.objects.get(
                authority=authority, user=caller)
            transaction_data = TransactionModelSerializer(
                transaction_queryset).data

            return MyResponse(request, {'status': 'ok', 'transaction': transaction_data},
                              status=status.HTTP_200_OK)

        except:
            return MyResponse(request, {'status': 'error', 'message': 'Transaction not found!'},
                              status=status.HTTP_404_NOT_FOUND)


@method_decorator(user_token_or_server_token_or_server_API_key_validation_and_authorization(accept_user_token=True),
                  name='post')
@method_decorator(serializer_validation_middleware(TransactionListInputSerializer), name='post')
class TransactionList(APIView):
    def post(self, request):

        skip = request.middleware_serializer_data.get('skip')
        take = request.middleware_serializer_data.get('take')
        success_filter = request.middleware_serializer_data.get(
            'success_filter')
        caller = request.middleware_user

        transactions_queryset = Transaction.objects.filter(
            user=caller)
        if success_filter == 'exclude':
            transactions_queryset = transactions_queryset.filter(status='NOK')
        elif success_filter == 'only':
            transactions_queryset = transactions_queryset.filter(status__in=[
                                                                 '100', '101'])
        transactions_queryset = transactions_queryset.order_by(
            "-modified_at")[skip:skip+take]
        transactions_data = TransactionModelSerializer(
            transactions_queryset, many=True).data

        return MyResponse(request, {'status': 'ok', 'transactions': transactions_data},
                          status=status.HTTP_200_OK)
