#!/usr/bin/python3

import argparse
import datetime as dt
from decimal import Decimal
import sys
from urllib.parse import urljoin
import xml.etree.ElementTree as ET

sys.path.insert(0, "/usr/local/mgr5/lib/python")
import billmgr.db as db
from billmgr.exception import XmlException
import billmgr.logger as logging
import billmgr.session as ses
import billmgr.misc as misc
from billmgr import payment
from billmgr.modules.paymethod import Feature, PaymethodModule, Param

from revolut.api import RevolutApi, WebhookEvent

MODULE = 'pmrevolut'

logging.init_logging(MODULE)
logger = logging.get_logger(MODULE)


class Revolut(PaymethodModule):
    def __init__(self) -> None:
        super().__init__()

        self.set_description('Revolut 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.PMSETPARAM, self.pm_setparam)
        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_feature(Feature.RP_SUCCESS)
        self._add_feature(Feature.RP_FAIL)

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

        self._add_param(Param.PAYMENT_SCRIPT, "/mancgi/revolutpayment")

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

    def pm_user_create(self):
        pass

    def pm_tune(self):
        xml = ses.get_input_xml()
        logger.info(f'xml = {ET.tostring(xml, encoding="unicode")}')
        xml.append(ses.new_node('outerlink', 'https://business.revolut.com/signup'))

        # TODO(v.drozdov) Отключаем возможность получения уведомлений от платежной системы
        xml.append(ses.new_node('create_webhook', 'off'))
        xml.append(ses.new_node(
            'webhook_url',
            urljoin(xml.get('host', default=''), '/mancgi/revolutresult')
        ))
        ET.dump(xml)

    def pm_setparam(self):
        xml = ses.get_input_xml()
        if xml.findtext('create_webhook', '') == 'on' and xml.findtext('webhook_id') is None:
            use_sandbox = xml.findtext('test', default='on') == 'on'
            secret_key = xml.findtext('secret_key', default='')
            api = RevolutApi(use_sandbox, secret_key)
            self.__create_webhook(xml, api)
        ET.dump(xml)

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

        use_sandbox = xml.findtext('test', default='on') == 'on'
        secret_key = xml.findtext('secret_key', default='')
        paymethod_currency = xml.findtext('paymethod/currency', default='')

        api = RevolutApi(use_sandbox, secret_key)
        if paymethod_currency:
            currency_iso = misc.get_currency_data(int(paymethod_currency)).iso
            api.check_currency(currency_iso)

        # выполняем любой немодифицирующий запрос, чтобы удостовериться,
        # что указанные авторизационные данные верны
        _ = api.retrieve_location_list()

    def rf_set(self):
        xml = ses.get_input_xml()
        logger.debug(f'xml = {ET.tostring(xml, encoding="utf8")}')

        payment_id = int(xml.findtext('source_payment', default=''))
        payment_description = xml.findtext('payment_description', default='')
        refund_amount = Decimal(xml.findtext('payment_paymethodamount', default=''))

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

        use_sandbox = method_params['test'] == 'on'
        secret_key = method_params['secret_key']
        api = RevolutApi(use_sandbox, secret_key)

        response = api.refund_order(external_id, RevolutApi.RefundOrderRequest(
            amount=-refund_amount,
            description=payment_description
        ))

        if response.state not in [
            RevolutApi.RefundOrderResponse.State.Completed,
            RevolutApi.RefundOrderResponse.State.Processing
        ]:
            raise XmlException('revolut_refund_failed', 'state', response.state.value)

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

    def __checkpay_no_lock(self):
        paymethods = db.db_query(
            ' SELECT id, xmlparams FROM paymethod'
            f' WHERE module = "{MODULE}" AND active = "on"')

        for rec in paymethods:
            xmlparams = ET.fromstring(rec.as_str('xmlparams'))
            self.__checkpay_by_paymethod(
                rec.as_int('id'),
                xmlparams.findtext('test', default='on') == 'on',
                xmlparams.findtext('secret_key', default='')
            )

    def __checkpay_by_paymethod(self, id: int, use_sandbox: bool, secret_key: str):
        api = RevolutApi(use_sandbox, secret_key)

        from_date = dt.datetime.today() - dt.timedelta(days = 7)
        payments = db.db_query(
            ' SELECT id, externalid FROM payment'
            f' WHERE createdate >= "{from_date.strftime("%Y-%m-%d")}"'
            f' AND paymethod = {id}'
            f' AND status = {payment.PaymentStatus.psInPay.value}'
        )

        for rec in payments:
            payment_id = rec.as_int('id')
            external_id = rec.as_str('externalid')

            try:
                response = api.retrieve_order(external_id)

                if response.type != RevolutApi.RetrieveOrderResponse.Type.Payment:
                    logger.info(f'ignored type "{response.type}" for payment #{payment_id}')
                    continue

                if response.state == RevolutApi.RetrieveOrderResponse.State.Completed:
                    payment.set_paid(payment_id, external_id)
                elif response.state == RevolutApi.RetrieveOrderResponse.State.Cancelled:
                    payment.set_canceled(payment_id, external_id)
                elif response.state == RevolutApi.RetrieveOrderResponse.State.Failed:
                    payment.set_canceled(payment_id, external_id)

            except XmlException:
                logger.warning(f'Failed to get state for payment #{payment_id}')

    def __create_webhook(self, xml: ET.Element, api: RevolutApi):
        # получаем список настроенных webhooks, чтобы определить
        # нужно ли установить новый webhook, и при необходимости,
        # если не найдено соответствующего url'а, создаем

        webhook_url = xml.findtext('webhook_url')
        if not webhook_url:
            return

        WEBHOOK_EVENTS = [
            WebhookEvent.OrderCompleted,
            WebhookEvent.OrderCancelled,
            WebhookEvent.OrderPaymentDeclined,
            WebhookEvent.OrderPaymentFailed
        ]
        webhooks = api.retrieve_webhook_list() # список уже настроенных вебхуков

        logger.info(f'webhook url = {webhook_url}')
        logger.info(f'already webhooks = {webhooks}')

        existing_webhook = next(filter(lambda wh: (wh.url == webhook_url), webhooks), None)

        if not existing_webhook:
            webhook = api.create_webhook(RevolutApi.CreateWebhookRequest(
                webhook_url,
                events=WEBHOOK_EVENTS
            ))
        else:
            webhook = api.retrieve_webhook(existing_webhook.id)

        if webhook.signing_secret is None:
            raise AssertionError()

        # сохраняем ключ и id вебхука в параметрах модуля оплаты
        xml.append(ses.new_node('webhook_id', webhook.id))
        xml.append(ses.new_node('webhook_signing_secret', webhook.signing_secret))


if __name__ == '__main__':
    Revolut().run()
