import csv
from hashlib import md5

from django.contrib import messages
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.db import DatabaseError, transaction
from django.db.models import Count
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, resolve_url
from django.urls import reverse_lazy
from django.utils import timezone
from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView, CreateView, UpdateView
from django.utils.translation import ugettext_lazy as _
from xmlview.decorators import xml_view
from djing import lib
from djing.global_base_views import OrderedFilteredList
from djing.lib.mixins import LoginAdminMixin, LoginAdminPermissionMixin
from finapp.forms import PayAllTimeGatewayForm
from finapp.models import AllTimePayLog, PayAllTimeGateway
from abonapp.models import Abon


class AllTimePay(DetailView):
    http_method_names = 'get',
    model = PayAllTimeGateway
    pk_url_kwarg = 'slug'
    slug_url_kwarg = 'pay_slug'

    @staticmethod
    def _bad_ret(err_id, err_description=None):
        now = timezone.now()
        r = {
            'status_code': lib.safe_int(err_id),
            'time_stamp': now.strftime("%d.%m.%Y %H:%M")
        }
        if err_description:
            r.update({'description': err_description})
        return r

    def check_sign(self, data: dict, sign: str) -> bool:
        act = lib.safe_int(data.get('ACT'))
        pay_account = data.get('PAY_ACCOUNT')
        serv_id = data.get('SERVICE_ID')
        pay_id = data.get('PAY_ID')
        md = md5()
        s = '_'.join(
            (str(act), pay_account or '', serv_id or '',
             pay_id or '', self.object.secret)
        )
        md.update(bytes(s, 'utf-8'))
        our_sign = md.hexdigest()
        return our_sign == sign

    @method_decorator(xml_view(root_node='pay-response'))
    def get(self, request, *args, **kwargs):
        self.object = self.get_object()
        act = lib.safe_int(request.GET.get('ACT'))
        self.current_date = timezone.now().strftime("%d.%m.%Y %H:%M")

        if act <= 0:
            return self._bad_ret(-101, 'ACT must be more than 0')
        sign = request.GET.get('SIGN')
        if not sign:
            return self._bad_ret(-101, 'SIGN not passed')
        if not self.check_sign(request.GET, sign.lower()):
            return self._bad_ret(-101, 'Bad sign')

        try:
            if act == 1:
                return self._fetch_user_info(request.GET)
            elif act == 4:
                return self._make_pay(request.GET)
            elif act == 7:
                return self._check_pay(request.GET)
            else:
                return self._bad_ret(-101, 'ACT is not passed')
        except Abon.DoesNotExist:
            return self._bad_ret(-40, 'Account does not exist')
        except DatabaseError:
            return self._bad_ret(-90)
        except AllTimePayLog.DoesNotExist:
            return self._bad_ret(-10)
        except AttributeError:
            return self._bad_ret(-101)

    def _fetch_user_info(self, data: dict):
        pay_account = data.get('PAY_ACCOUNT')
        abon = Abon.objects.get(username=pay_account)
        fio = abon.fio
        ballance = float(abon.ballance)
        return {
            'balance': round(ballance, 2),
            'name': fio,
            'account': pay_account,
            'service_id': self.object.service_id,
            'min_amount': 10.0,
            'max_amount': 5000,
            'status_code': 21,
            'time_stamp': self.current_date
        }

    def _make_pay(self, data: dict):
        trade_point = lib.safe_int(data.get('TRADE_POINT'))
        receipt_num = lib.safe_int(data.get('RECEIPT_NUM'))
        pay_account = data.get('PAY_ACCOUNT')
        pay_id = data.get('PAY_ID')
        pay_amount = lib.safe_float(data.get('PAY_AMOUNT'))
        abon = Abon.objects.get(username=pay_account)
        pays = AllTimePayLog.objects.filter(pay_id=pay_id)
        if pays.exists():
            return self._bad_ret(-100, 'Pay already exists')

        with transaction.atomic():
            abon.add_ballance(
                None, pay_amount,
                comment='%s %.2f' % (self.object.title, pay_amount)
            )
            abon.save(update_fields=('ballance',))

            AllTimePayLog.objects.create(
                pay_id=pay_id,
                summ=pay_amount,
                abon=abon,
                trade_point=trade_point,
                receipt_num=receipt_num,
                pay_gw=self.object
            )
        return {
            'pay_id': pay_id,
            'service_id': data.get('SERVICE_ID'),
            'amount': round(pay_amount, 2),
            'status_code': 22,
            'time_stamp': self.current_date
        }

    def _check_pay(self, data: dict):
        pay_id = data.get('PAY_ID')
        pay = AllTimePayLog.objects.get(pay_id=pay_id)
        return {
            'status_code': 11,
            'time_stamp': self.current_date,
            'transaction': {
                'pay_id': pay_id,
                'service_id': data.get('SERVICE_ID'),
                'amount': round(pay.summ, 2),
                'status': 111,
                'time_stamp': pay.date_add.strftime("%d.%m.%Y %H:%M")
            }
        }


class BasicFinReport(LoginAdminMixin, ListView):
    model = AllTimePayLog
    queryset = AllTimePayLog.objects.by_days()
    template_name = 'finapp/fin_report.html'
    context_object_name = 'logs'

    def get(self, request, *args, **kwargs):
        res_format = request.GET.get('f')
        if res_format == 'csv':
            response = HttpResponse(content_type='text/csv')
            response['Content-Disposition'] = 'attachment; filename="report.csv"'
            writer = csv.writer(response, quoting=csv.QUOTE_NONNUMERIC)
            for row in self.object_list:
                writer.writerow(
                    (row['summ'], row['pay_date'].strftime('%Y-%m-%d'))
                )
            return response
        return super().get(request, *args, **kwargs)


class PayHistoryListView(LoginAdminMixin, PermissionRequiredMixin,
                         OrderedFilteredList):
    permission_required = 'group_app.view_group'
    context_object_name = 'pay_history'
    template_name = 'finapp/payHistory.html'
    model = AllTimePayLog

    def get_queryset(self):
        pay_history = AllTimePayLog.objects.filter(
            pay_gw__slug=self.kwargs.get('pay_slug')
        ).select_related('abon__group').order_by('-date_add')
        return pay_history

    def get_context_data(self, **kwargs):
        context = {
            'pay_gw': get_object_or_404(PayAllTimeGateway, slug=self.kwargs.get('pay_slug'))
        }
        context.update(kwargs)
        return super(PayHistoryListView, self).get_context_data(**context)


class AddAllTimeGateway(LoginAdminMixin, PermissionRequiredMixin, CreateView):
    permission_required = 'finapp.add_payalltimegateway'
    model = PayAllTimeGateway
    form_class = PayAllTimeGatewayForm
    success_url = reverse_lazy('finapp:alltime_gateways_list')

    def form_valid(self, form):
        messages.success(self.request, _('New pay gateway created successfully'))
        return super(AddAllTimeGateway, self).form_valid(form)


class AllTimeGatewaysListView(LoginAdminPermissionMixin, ListView):
    permission_required = 'finapp.view_payalltimegateway'
    model = PayAllTimeGateway
    queryset = PayAllTimeGateway.objects.annotate(
        pays_count=Count('alltimepaylog')
    )


class EditPayUpdateView(LoginAdminPermissionMixin, UpdateView):
    permission_required = 'finapp.change_payalltimegateway'
    model = PayAllTimeGateway
    form_class = PayAllTimeGatewayForm
    pk_url_kwarg = 'slug'
    slug_url_kwarg = 'pay_slug'

    def get_success_url(self):
        return resolve_url('finapp:edit_pay_gw', self.object.slug)

    def form_valid(self, form):
        messages.success(self.request, _('Payment gateway successfully updated'))
        return super(EditPayUpdateView, self).form_valid(form)
