#!/usr/bin/env python3

import argparse
import datetime as dt
import xml.etree.ElementTree as ET
import sys

sys.path.insert(0, "/usr/local/mgr5/lib/python")

from billmgr.modules.paymethod import PaymentBehavior, PaymentTypeInfo, PaymethodModule, Feature, Param, RecurringType, recurring_type_param
import billmgr.db as db
import billmgr.exception as exc
import billmgr.misc as misc
import billmgr.logger as logging
import billmgr.payment as payment
import billmgr.session as session

import paypalexpanded.api as paypal_api

logging.init_logging("pmpaypalexpanded")
logger = logging.get_logger("pmpaypalexpanded")


MODULE_NAME = 'pmpaypalexpanded'


class PayPalExpanded(PaymethodModule):
    def __init__(self) -> None:
        super().__init__()
        self.set_description("PayPal Expanded Integration")

        self._add_feature(Feature.REDIRECT)
        self._add_feature(Feature.NOT_PROFILE)

        self._add_callable_feature(Feature.PMUSERCREATE, self.pm_user_create)
        self._add_callable_feature(Feature.PMTUNE, self.pm_tune)
        self._add_callable_feature(Feature.PMVALIDATE, self.pm_validate)

        self._add_feature(Feature.REFUND)
        self._add_callable_feature(Feature.RFSET, self.rf_set)
        self._add_callable_feature(Feature.CRSET, self.cr_set)


        self._add_feature(Feature.RP_SUCCESS)
        self._add_feature(Feature.RP_FAIL)

        self._add_feature(Feature.RECURRING)
        self._add_feature(Feature.STORED)
        self._add_callable_feature(Feature.RCPAY, self.rc_pay)

        self._add_callable_feature(Feature.CHECKPAY, self.checkpay)

        self._add_param(Param.PAYMENT_SCRIPT, "/mancgi/ppexpandedpayment")
        self._add_param(Param.RECURRING_SCRIPT, "/mancgi/ppexpandedrecurring")
        self._add_param(Param.RECURRING_TYPE, recurring_type_param([RecurringType.rtMaxAmount, RecurringType.rtRedirect]))

        self._set_paymethod_payment_type("payment_method", "",
        [
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "apple_pay", False, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "google_pay", False, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "card", True, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "paypal", True, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "bancontact", False, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "blik", False, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "eps", False, {}),
            # PaymentTypeInfo(PaymentBehavior.pbRedirect, "giropay", False, {}), deprecated by paypal
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "ideal", False, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "mybank", False, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "p24", False, {}),
            # PaymentTypeInfo(PaymentBehavior.pbRedirect, "sofort", False, {}), deprecated by paypal
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "venmo", True, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "pay_upon_invoice", False, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "multibanco", False, {}),
            PaymentTypeInfo(PaymentBehavior.pbRedirect, "trustly", False, {}),
        ])


    def _on_raise_exception(self, args: argparse.Namespace, err: exc.XmlException):
        print(err.as_xml())

    def cr_set(self):
        pass

    def pm_user_create(self):
        pass

    def pm_tune(self):
        xml = session.get_input_xml()
        xml.append(session.new_node("outerlink", "https://www.paypal.com/ru/cgi-bin/webscr?cmd=_biz-signup-flow"))
        ET.dump(xml)

    def pm_validate(self):
        xml = session.get_input_xml()

        client_id = xml.findtext("client_id", default="")
        secret = xml.findtext("secret", default="")
        is_test = xml.findtext("test", default="on") == "on"
        # webhook_id = xml.findtext("webhook_id", default="")

        paypal_api.authenticate(paypal_api.AuthData(client_id, secret, is_test))

        paymethod_id = xml.findtext("paymethod/id", default="")
        paymethod_currency = xml.findtext("paymethod/currency", default="")
        paymethods = xml.findtext("payment_method", default="").split(',')

        if paymethod_id:
            currency_iso = misc.get_currency_data(int(paymethod_currency)).iso
            for pm, supported_currencies in paypal_api.SUPPORTED_CURRENCIES.items():
                if pm in paymethods and currency_iso not in supported_currencies:
                    exception = exc.XmlException("pp_unsupported_currency")
                    exception.add_param("currencies", ', '.join(supported_currencies))
                    exception.add_param("pm", pm)
                    raise exception

    def rf_set(self):
        xml = session.get_input_xml()

        logger.debug(f'xml = {ET.tostring(xml, encoding="utf8")}')

        payment_id = int(xml.findtext('source_payment', default=""))
        refund_payment_id = int(xml.findtext('payment', default=""))

        payment_params = self.get_payment_params(payment_id)
        method_params = self.get_method_params(int(payment_params['paymethod']))

        paymethod_refund_amount = paypal_api.amount_from_str(xml.findtext('payment_paymethodamount', default=''))
        paymethod_refund_amount = -paymethod_refund_amount

        auth = paypal_api.authenticate(paypal_api.AuthData(
            method_params["client_id"],
            method_params["secret"],
            method_params["test"] == "on"
        ))

        capture_id = payment_params['externalid'].split('/')[1]

        is_full_refund = payment_id == refund_payment_id
        if is_full_refund:
            response = paypal_api.request_refund_full_captured_order(
                auth,
                capture_id,
                invoice=str(refund_payment_id)
            )
        else:
            paymethod_currency = xml.findtext('paymethod_currency', default="")
            response = paypal_api.request_refund_captured_order(
                auth,
                capture_id,
                amount=paymethod_refund_amount,
                currency_iso=misc.get_currency_data(int(paymethod_currency)).iso,
                invoice=str(refund_payment_id)
            )

        json = response.json()
        status = json['status']

        if status == paypal_api.RefundStatus.COMPLETED.name:
            logger.debug('refund completed')

        elif status == paypal_api.RefundStatus.PENDING.name:
            reason = json.get('status_details', {}).get('reason', '')
            logger.warning(f'refund pending: {reason}')
            raise paypal_api.RefundStatusFailedError('status_pending')

        elif status == paypal_api.RefundStatus.CANCELLED.name:
            raise paypal_api.RefundStatusCancelledError()

        elif status == paypal_api.RefundStatus.FAILED.name:
            reason = json.get('status_details', {}).get('reason', '')
            raise paypal_api.RefundStatusFailedError(reason)


    def rc_pay(self, payment_id: int):
        payment_params = self.get_payment_params(payment_id)
        logger.debug(f'payment_params = {payment_params}')
        
        method_params = self.get_method_params(int(payment_params['paymethod']))
        logger.debug(f'method_param = {method_params}')

        recurring_params = self.get_recurring_params(int(payment_params['recurring']))
        logger.debug(f'recurring_params = {recurring_params}')
        currency_iso: str = payment.PaymentInfo(payment_id, language=None).currency["iso"] # type: ignore
        
        try:
            auth = paypal_api.authenticate(paypal_api.AuthData(
                method_params["client_id"],
                method_params["secret"],
                method_params["test"] == "on"
            ))

            response = paypal_api.request_create_order(
                auth,
                order=paypal_api.Order(
                    amount=paypal_api.amount_from_str(payment_params["amount"]),
                    currency_iso=currency_iso,
                    reference_id=str(payment_id),
                    need_save=False,
                    urls=paypal_api.Urls(
                        return_url="",
                        cancel_url="",
                    )
                ),
                paymethod=recurring_params['data2'],
                saved_token=recurring_params['token']
            )

            if response.json()['status'] != 'COMPLETED':
                raise exc.XmlException("pp_autopayment_failure")

            captured_order = paypal_api.parse_capture_order(response)
            external_id = f'{captured_order.order_id}/{captured_order.capture_id}'
            payment.set_paid(payment_id, external_id)

        except Exception as e:
            payment.set_canceled(payment_id, "")
            raise e

    def checkpay(self):
        with misc.FileLock(f"var/run/lock/{MODULE_NAME}_{Feature.CHECKPAY}") as _:
            self.__checkpay_no_lock()

    def __checkpay_no_lock(self):
        from_date = dt.datetime.today() - dt.timedelta(days = 7)

        records = db.db_query(
            ' SELECT p.id, p.externalid, p.xmlparams AS payment_xmlparams, pm.xmlparams AS paymethod_xmlparams'
            ' FROM payment p'
            ' JOIN paymethod pm ON pm.id = p.paymethod'
            f' WHERE module = "{MODULE_NAME}"'
            f' AND p.status = {payment.PaymentStatus.psInPay.value}'
            f' AND p.createdate >= \'{from_date.strftime("%Y-%m-%d")}\''
        )

        for rec in records:
            paymethod_xmlparams = ET.fromstring(rec.as_str('paymethod_xmlparams'))

            auth = paypal_api.authenticate(paypal_api.AuthData(
                paymethod_xmlparams.findtext('client_id', default=''),
                paymethod_xmlparams.findtext('secret', default=''),
                paymethod_xmlparams.findtext('test', default='') == 'on'
            ))

            payment_id = rec.as_int('id')
            external_id = rec.as_str('externalid')

            external_ids = external_id.split('/')
            if len(external_ids) != 2:
                continue
            order_id = external_ids[0]
            capture_id = external_ids[1]

            try:
                response = paypal_api.request_show_captured_payment_details(auth, capture_id)
                json = response.json()
                status = json['status']

                if status == paypal_api.CaptureStatus.COMPLETED.name:
                    payment_xmlparams = ET.fromstring(rec.as_str('payment_xmlparams'))

                    if payment_xmlparams.findtext('store_payment', default='') == 'on':
                        response = paypal_api.request_show_order_details(auth, order_id)
                        stored_token = paypal_api.get_stored_token(response)
                        if stored_token is None:
                            raise paypal_api.TokenNotFoundError()

                        payment.set_paid(payment_id, external_id, payment.RecurringInfo(
                            status=payment.RecurringStatus.rsStored,
                            token=stored_token,
                            data1='',
                            data2=payment_xmlparams.findtext('payment_method', default=''),
                            name=''
                        ))

                    else:
                        payment.set_paid(payment_id, external_id)


                elif status == paypal_api.CaptureStatus.DECLINED.name:
                    payment.set_canceled(payment_id, external_id)

                elif status == paypal_api.CaptureStatus.FAILED.name:
                    payment.set_canceled(payment_id, external_id)

            except:
                logger.warning(f'error when processing payment {external_id}')


if __name__ == "__main__":
    PayPalExpanded().run()
