# eopayment - online payment library
# Copyright (C) 2011-2020 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import re
import random
from decimal import Decimal, ROUND_DOWN

from .common import (PaymentCommon, PaymentResponse, URL, PAID, DENIED,
                     CANCELLED, ERROR, ResponseError)
from six.moves.urllib.parse import urlencode, parse_qs

from gettext import gettext as _
import logging
import warnings

from .systempayv2 import isonow

__all__ = ['Payment']

TIPI_URL = 'https://www.tipi.budget.gouv.fr/tpa/paiement.web'
LOGGER = logging.getLogger(__name__)


class Payment(PaymentCommon):
    '''Produce requests for and verify response from the TIPI online payment
    processor from the French Finance Ministry.

    '''

    description = {
        'caption': 'TIPI, Titres Payables par Internet',
        'parameters': [
            {
                'name': 'numcli',
                'caption': _(u'Client number'),
                'help_text': _(u'6 digits number provided by DGFIP'),
                'validation': lambda s: str.isdigit(s) and (0 < int(s) < 1000000),
                'required': True,
            },
            {
                'name': 'service_url',
                'default': TIPI_URL,
                'caption': _(u'TIPI service URL'),
                'help_text': _(u'do not modify if you do not know'),
                'validation': lambda x: x.startswith('http'),
                'required': True,
            },
            {
                'name': 'normal_return_url',
                'caption': _('Normal return URL (unused by TIPI)'),
                'required': False,
            },
            {
                'name': 'automatic_return_url',
                'caption': _('Automatic return URL'),
                'required': True,
            },
            {
                'name': 'saisie',
                'caption': _('Payment type'),
                'required': True,
                'default': 'T',
                'choices': [
                    ('T', _('test')),
                    ('X', _('production')),
                    ('A', _('with user account')),
                    ('M', _('manual entry')),
                ],
            },
        ],
    }

    REFDET_RE = re.compile('^[a-zA-Z0-9]{6,30}$')

    def _generate_refdet(self):
        return '%s%010d' % (isonow(), random.randint(1, 1000000000))

    def request(self, amount, next_url=None, exer=None, orderid=None,
                refdet=None, objet=None, email=None, saisie=None, **kwargs):
        try:
            montant = Decimal(amount)
            if Decimal('0') > montant > Decimal('9999.99'):
                raise ValueError('MONTANT > 9999.99 euros')
            montant = montant * Decimal('100')
            montant = montant.to_integral_value(ROUND_DOWN)
        except ValueError:
            raise ValueError(
                'MONTANT invalid format, must be '
                'a decimal integer with less than 4 digits '
                'before and 2 digits after the decimal point '
                ', here it is %s' % repr(amount))

        automatic_return_url = self.automatic_return_url
        if next_url and not automatic_return_url:
            warnings.warn("passing next_url to request() is deprecated, "
                          "set automatic_return_url in options", DeprecationWarning)
            automatic_return_url = next_url
        if automatic_return_url is not None:
            if (not isinstance(automatic_return_url, str)
                    or not automatic_return_url.startswith('http')):
                raise ValueError('URLCL invalid URL format')
        try:
            if exer is not None:
                exer = int(exer)
                if exer > 9999:
                    raise ValueError()
        except ValueError:
            raise ValueError('EXER format invalide')
        assert not (orderid and refdet), 'orderid and refdet cannot be used together'
        # check or generate refdet
        if refdet:
            try:
                if not self.REFDET_RE.match(refdet):
                    raise ValueError
            except (TypeError, ValueError):
                raise ValueError('refdet must be 6 to 30 alphanumeric characters string')
        if orderid:
            if self.REFDET_RE.match(orderid):
                refdet = orderid
            else:
                objet = orderid + (' ' + objet) if objet else ''
        if not refdet:
            refdet = self._generate_refdet()
        transaction_id = refdet
        # check objet or fix objet
        if objet is not None:
            try:
                objet = objet.encode('ascii')
            except Exception as e:
                raise ValueError('OBJET must be an alphanumeric string', e)
        try:
            mel = str(email)
            if '@' not in mel:
                raise ValueError('no @ in MEL')
            if not (6 <= len(mel) <= 80):
                raise ValueError('len(MEL) is invalid, must be between 6 and 80')
        except Exception as e:
            raise ValueError('MEL is not a valid email, %r' % mel, e)

        # check saisie
        saisie = saisie or self.saisie
        if saisie not in ('M', 'T', 'X', 'A'):
            raise ValueError('SAISIE invalid format, %r, must be M, T, X or A' % saisie)

        params = {
            'numcli': self.numcli,
            'refdet': refdet,
            'montant': montant,
            'mel': mel,
            'saisie': saisie,
        }
        if exer:
            params['exer'] = exer
        if objet:
            params['objet'] = objet
        if automatic_return_url:
            params['urlcl'] = automatic_return_url
        url = '%s?%s' % (self.service_url, urlencode(params))
        return transaction_id, URL, url

    def response(self, query_string, **kwargs):
        fields = parse_qs(query_string, True)
        if not set(fields) >= set(['refdet', 'resultrans']):
            raise ResponseError('missing refdet or resultrans')
        for key, value in fields.items():
            fields[key] = value[0]
        refdet = fields.get('refdet')
        if refdet is None:
            raise ResponseError('refdet is missing')

        result = fields.get('resultrans')
        if result == 'P':
            result = PAID
            bank_status = ''
        elif result == 'R':
            result = DENIED
            bank_status = 'refused'
        elif result == 'A':
            result = CANCELLED
            bank_status = 'canceled'
        else:
            bank_status = 'wrong return: %r' % result
            result = ERROR

        test = fields.get('saisie') == 'T'

        return PaymentResponse(
            result=result,
            bank_status=bank_status,
            signed=True,
            bank_data=fields,
            order_id=refdet,
            transaction_id=refdet,
            test=test)
