import io
from csv import DictReader
from dataclasses import dataclass
from datetime import datetime, date
from decimal import Decimal
from enum import Enum
from typing import List

from urllib.parse import urlencode

from ludialudom.errors import SignatureError
from ludialudom.resources.base import BaseResource


class Payment(BaseResource):
    class PaymentState(Enum):
        ACTIVE = 1
        SUSPENDED = 2
        CANCELED = 3
        INVALID_CARD = 4

    @dataclass(frozen=True)
    class TransactionItem(object):
        class Status(Enum):
            CANCELED = 'canceled'
            DELETED = 'deleted'
            EXPIRED = 'expired'
            SUCCESSFUL = 'successful'
            UNSUCCESSFUL = 'unsuccessful'
            WAITING = 'waiting'
            WIN = 'win'
            NONE = 'none'
            IN_PROGRESS = 'in_progress'
            PENDING = 'pending'

        project_code: int
        widget: str
        donated_at: datetime
        amount: Decimal
        variable_symbol: str
        name: str
        surname: str
        email: str
        payment_method_id: int
        payment_method_description: str
        status_id: int
        status_description: str
        status_code: Status
        sequence_no: int

    @dataclass(frozen=True)
    class SlipItem(object):
        project_code: int
        external_identifier: int
        slip_amount: Decimal
        amount: Decimal
        donated_at: datetime
        variable_symbol: str
        donor_name: str
        donor_surname: str
        donor_email: str
        widget: str
        sequence_no: int

    """
    Method returns redirect URL for payment
    """
    def create_payment_url(self, payment_id: int, project_code: int, channel: int, amount: Decimal, name: str,
                           surname: str, email: str, start_date: datetime = None, end_date: datetime = None,
                           is_mobile: bool = False) -> str:
        payload = {
            'clientId': self._configuration.client_id,
            'paymentId': payment_id,
            'projectCode': project_code,
            'paymentChannel': channel,
            'amount': amount,
            'name': name,
            'surname': surname,
            'email': email,
            'mobileDevice': 'true' if is_mobile else 'false',
            'sign': self._sign(f"{payment_id}{project_code}{channel}{amount}{name}{surname}{email}")
        }

        if start_date:
            payload['startDate'] = start_date.strftime("%Y-%m-%d %H:%M:%S")

        if end_date:
            payload['endDate'] = end_date.strftime("%Y-%m-%d %H:%M:%S")

        return f"{self._configuration.environment.web_url}/pay?{urlencode(payload)}"

    def change_amount(self, payment_id: int, amount: Decimal) -> bool:
        payload = {
            'paymentId': payment_id,
            'newAmount': amount,
            'sign': self._sign(f"{payment_id}{amount}")
        }

        response = self.requestor.request('POST', '/changeComfortPay', payload=payload)

        if response.get('sign') != self._sign(f"{response.get('result')}{response.get('code')}"):
            raise SignatureError()

        return 'OK' == response.get('result')

    def change_state(self, payment_id: int, state: PaymentState) -> "Payment.PaymentState":
        payload = {
            'paymentId': payment_id,
            'status': state.value,
            'sign': self._sign(f"{payment_id}{state.value}")
        }

        response = self.requestor.request('POST', '/setComfortPayStatus', payload=payload)
        response_sign = self._sign(f"{response.get('result')}{response.get('code')}{response.get('status')}")

        if response.get('sign') != response_sign:
            raise SignatureError()

        return Payment.PaymentState(response['status'])

    def get_state(self, payment_id: int) -> "Payment.PaymentState":
        payload = {
            'paymentId': payment_id,
            'sign': self._sign(str(payment_id))
        }

        response = self.requestor.request('POST', '/getComfortPayStatus', payload=payload)
        response_sign = self._sign(f"{response.get('result')}{response.get('code')}{response.get('status')}")

        if response.get('sign') != response_sign:
            raise SignatureError()

        return Payment.PaymentState(response['status'])

    def transactions(self, project_code: int,
                     date_from: date = None, date_to: date = None) -> List["Payment.TransactionItem"]:
        payload = {
            'projectCode': project_code,
            'sign': self._sign(f"{project_code}")
        }

        if date_from is not None:
            payload['from'] = date_from.strftime("%Y-%m-%d")

        if date_to is not None:
            payload['to'] = date_to.strftime("%Y-%m-%d")

        response = self.requestor.request('GET', '/GetPayments', query=payload, parse=False)

        if self.configuration.download_path is not None:
            with open(f"{self.configuration.download_path}/{project_code}-transactions.csv", "w") as csv_file:
                csv_file.write(response)

        reader = DictReader(io.StringIO(response), delimiter=';')
        result = []

        for item in reader:
            result.append(self.TransactionItem(
                project_code=int(item['projectCode']),
                widget=item[' id_platba_widget_ext'].strip() or None,
                donated_at=datetime.strptime(item[' d_zadania'], "%Y-%m-%d %H:%M:%S"),
                amount=Decimal(item[' i_suma'].replace(",", ".")),
                variable_symbol=item[' s_vs'].strip(),
                name=item[' s_meno'].strip() or None,
                surname=item[' s_priezvisko'].strip() or None,
                email=item[' s_email'].strip() or None,
                payment_method_id=int(item[' id_transakcia_sposob']),
                payment_method_description=item[' s_transakcia_sposob'],
                status_id=int(item[' id_transakcia_stav']),
                status_description=item[' s_transakcia_stav'],
                status_code=self.TransactionItem.Status(item[' s_stav'].strip()),
                sequence_no=int(item[' i_poradove_cislo'])
            ))

        return result

    def slips(self, project_code: int, random: int = 868) -> List["Payment.SlipItem"]:
        payload = {
            'projectCode': project_code,
            'random': random,
            'sign': self._sign(f"{random}")
        }

        response = self.requestor.request('GET', '/GetRecipientPayments', query=payload, parse=False)

        if self.configuration.download_path is not None:
            with open(f"{self.configuration.download_path}/{project_code}-slips.csv", "w") as csv_file:
                csv_file.write(response)

        reader = DictReader(io.StringIO(response), delimiter=';')
        result = []

        for item in reader:
            result.append(self.SlipItem(
                project_code=int(item['s_kod_vyzvy']),
                external_identifier=int(item['id_transakcia']),
                slip_amount=Decimal(item['i_suma_transakcie'].replace(",", ".")),
                donated_at=datetime.strptime(item['d_uhrady'], "%Y-%m-%d %H:%M:%S"),
                variable_symbol=item['s_vs'],
                amount=Decimal(item['i_suma_daru'].replace(",", ".")),
                donor_name=item['s_meno'].strip() or None,
                donor_surname=item['s_priezvisko'].strip() or None,
                donor_email=item['s_email'].strip() or None,
                widget=item['id_platba_widget_ext'].strip() or None,
                sequence_no=int(item['i_poradove_cislo'])
            ))

        return result
