"""
Models
======

"""
import typing
from enum import Enum
from typing import Dict


class TransactionType(Enum):
    PUSH_TO_PAY = ('0200', '040000', 'Push To Pay')
    REVERSAL = ('0400', '040000', 'Reversal Transaction')
    VOID = ('0200', '020040', 'Void Transaction')


class AppSource(Enum):
    EDC = 'EDC'
    POS = 'POS'


class ResponseCode(Enum):
    """ List of possible response code from API

    """

    def __new__(cls, value, phrase='', description=''):
        obj = object.__new__(cls)
        obj._value_ = value

        obj.phrase = phrase
        obj.description = description
        return obj

    SUCCESS = '00', 'Success', 'Success'
    VOUCHER_USED = '8', 'Voucher Code Already Used', 'Failed to redeem deal, voucher already used'
    INVALID_ACCOUNT_TYPE = '11', 'Invalid Account Type', 'Invalid user level, unable to process cash withdrawal'
    INVALID_AMOUNT = '13', 'Invalid Amount', 'Using amount less than equal zero'
    INVALID_OVO_ID = '14', 'Invalid Mobile Number/OVO ID', 'Phone number/OVO ID not found in OVO system'
    INVALID_CARD = '16', 'Invalid Card', 'Invalid card number'
    TRANSACTION_DECLINE = '17', 'Transaction Decline', 'OVO USer cancel payment using OVO Apps'
    INVALID_VOUCHER_CODE = '18', 'Invalid Voucher Code', 'Failed to redeem deal, invalid voucher code'
    TRANSACTION_FAILED = '26', 'Transaction Failed', 'Failed push payment confirmation to OVO Apps'
    FORMAT_ERROR = '30', 'Format Error', 'Invalid ISO Spec'
    TRANSACTION_ERROR_PARTY = '40', 'Transaction Failed 3rd Party', 'Error in 3rd party, including failed to deduct/topup e-money'
    EXPIRED_RETURN_CODE = '44', 'Expired Return Code', 'Refunc code is no longer valid (over the time limit'
    INSUFFICIENT_FUND = '51', 'Insufficient Fund', 'Fund in card is not enought to make payment'
    CARD_BLOCKED = '56', 'Card Blocked. Please Call', 'Card is blocked, unable to process card transaction'
    TRANSACTION_FAILED_REFUND = '57', 'Transaction Failed', 'Not eligible to do refund, siloam balance = 0'
    TRANSACTION_NOT_ALLOWED = '58', 'Transaction Not Allowed', 'Not valid transaction in merchant/terminal'
    EXCEED_LIMIT = '61', 'Exceed Transaction Limit', 'Amount/count exceed limit, set by user'
    SECURITY_VIOLATION = '63', 'Security Violation', 'Authentication failed'
    ACCOUNT_BLOCKED = '64', 'Account Blocked. Please call', 'Account is blocked, unable to process transaction'
    TRANSACTION_FAILED_LIMIT = '65', 'Transaction Failed', 'Limit transaction exceeded, limit on count or amount'
    INVALID_CODE = '66', 'Invalid Code', 'Wrong input withdraw code'
    BELOW_TRANSACTION_LIMIT = '67', 'Below Transaction Limit', 'The transaction amount is less than the minimum payment'
    SYSTEM_FAILURE = 'ER', 'System Failure', 'There is an error in OVO system'
    TERMINAL_BLOCKED = 'EB', 'Terminal Blocked', 'Not registered / blocked terminal'
    NOT_FOUND = '404', 'Expired Push Payment/customer ignore transaction'


class TransactionRequest:
    __slots__ = (
        'batch_no', 'phone', 'merchant_invoice'
    )

    def __init__(self, batch_no: typing.Text, phone: typing.Text, merchant_invoice: str):
        """

        :type merchant_invoice: str
        :param batch_no: Batch number of transaction, for settlement, max 6 digit
        :param phone: Phone number or Loyalty ID customer already registered on OVO
        :param merchant_invoice: Invoice number generated by merchant, must be unique, max length 50 char
        """
        self.batch_no = batch_no
        self.phone = phone
        self.merchant_invoice = merchant_invoice

    def serialize(self):
        return {
            'batchNo': self.batch_no,
            'phone': self.phone,
            'merchantInvoice': self.merchant_invoice
        }

    @classmethod
    def from_api_json(cls, api_json):
        return cls(
            merchant_invoice=api_json.get('merchantInvoice'),
            batch_no=api_json.get('batchNo'),
            phone=api_json.get('phone')
        )


class TransactionResponseData:
    __slots__ = (
        'ovoid', 'store_code', 'cash_used', 'store_address_1', 'store_address_2', 'ovo_points_used',
        'ovo_points_balance', 'payment_type'
    )
    def __init__(self, ovoid: str, store_code: str, cash_used: int,
                 store_address_1: str, store_address_2: str,
                 ovo_points_used: str, ovo_points_balance: str,
                 payment_type: str):
        """

        :param ovoid:
        :param store_code:
        :param cash_used:
        :param store_address_1:
        :param store_address_2:
        :param ovo_points_used:
        :param ovo_points_balance:
        :param payment_type:
        """
        self.ovoid = ovoid
        self.store_code = store_code
        self.cash_used = cash_used
        self.store_address_1 = store_address_1
        self.store_address_2 = store_address_2
        self.ovo_points_used = ovo_points_used
        self.ovo_points_balance = ovo_points_balance
        self.payment_type = payment_type

    @classmethod
    def from_api_json(cls, api_json: Dict):
        if api_json:
            return cls(
                ovoid=api_json.get('ovoid'),
                store_code=api_json.get('storeCode'),
                cash_used=api_json.get('cashUsed'),
                store_address_1=api_json.get('storeAddress1'),
                store_address_2=api_json.get('storeAddress2'),
                ovo_points_used=api_json.get('ovoPointsUsed'),
                ovo_points_balance=api_json.get('ovoPointsBalance'),
                payment_type=api_json.get('paymentType')
            )
        else:
            return


class PaymentRequest:
    def __init__(self, transaction_type: TransactionType, amount: int, date: str, reference_number: str,
                 tid: str, mid: str, merchant_id: str, store_code: str,
                 app_source: AppSource, transaction_request_data: TransactionRequest):
        """

        :param transaction_type:
        :param amount:
        :param date:
        :param reference_number:
        :param tid:
        :param mid:
        :param merchant_id:
        :param store_code:
        :param app_source:
        :param transaction_request_data:
        """
        self.transaction_type = transaction_type
        self.request_type, self.processing_code, self.description = self.transaction_type.value
        self.amount = amount
        self.date = date
        self.reference_number = reference_number
        self.tid = tid
        self.mid = mid
        self.merchant_id = merchant_id
        self.store_code = store_code
        self.app_source = app_source
        self.transaction_request_data = transaction_request_data

    def serialize(self):
        return {
            'type': self.request_type,
            'processingCode': self.processing_code,
            'amount': self.amount,
            'date': self.date,
            'referenceNumber': self.reference_number,
            'tid': self.tid,
            'mid': self.mid,
            'merchantId': self.merchant_id,
            'storeCode': self.store_code,
            'appSource': self.app_source.value,
            'transactionRequestData': self.transaction_request_data.serialize()
        }


class PaymentResponse:
    __slots__ = (
        'payment_type', 'processing_code', 'amount', 'date', 'trace_number', 'host_time', 'host_date',
        'reference_number',
        'approval_code', 'response_code', 'tid', 'mid', 'transaction_request_data', 'transaction_response_data',
        'response_status_message'
    )

    def __init__(self, payment_type: str, processing_code: str, amount: int, date: str, trace_number: str,
                 host_time: str, host_date: str, reference_number: str,
                 response_code: str, tid: str, mid: str, transaction_request_data: TransactionRequest,
                 transaction_response_data: TransactionResponseData = None, approval_code: str = None):
        self.payment_type = payment_type
        self.processing_code = processing_code
        self.amount = amount
        self.date = date
        self.trace_number = trace_number
        self.host_time = host_time
        self.host_date = host_date
        self.reference_number = reference_number
        self.response_code = response_code
        self.tid = tid
        self.mid = mid
        self.transaction_request_data = transaction_request_data
        self.transaction_response_data = transaction_response_data
        self.approval_code = approval_code if approval_code else ''
        self.response_status_message = self.response_status.phrase

    @property
    def response_status(self):
        return ResponseCode(self.response_code)

    @property
    def is_success(self):
        return self.response_status.name == 'SUCCESS'

    @classmethod
    def from_api_json(cls, api_json: Dict):
        return cls(
            payment_type=api_json.get('type'),
            processing_code=api_json.get('processingCode'),
            amount=api_json.get('amount'),
            date=api_json.get('date'),
            trace_number=api_json.get('traceNumber'),
            host_time=api_json.get('hostTime'),
            host_date=api_json.get('hostDate'),
            reference_number=api_json.get('referenceNumber'),
            response_code=api_json.get('responseCode', ResponseCode.NOT_FOUND.value),
            tid=api_json.get('tid'),
            mid=api_json.get('mid'),
            transaction_request_data=TransactionRequest.from_api_json(api_json.get('transactionRequestData')),
            transaction_response_data=TransactionResponseData.from_api_json(api_json.get('transactionResponseData', {})),
            approval_code=api_json.get('approvalCode')
        )
