# passerelle - uniform access to multiple data sources and services
# Copyright (C) 2022  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 json

from django.db import models
from django.utils.translation import gettext_lazy as _

from passerelle.sms.models import SMSResource
from passerelle.utils.jsonresponse import APIError

GSM_CHARACTERS = {
    chr(char)
    for char in [
        # https://unicode.org/Public/MAPPINGS/ETSI/GSM0338.TXT
        # GSM 03.38 characters unicode values, in GSM encoding order
        # Unicode  # char - GSM - Name
        0x0040,  # @ - 0x00 - COMMERCIAL AT
        0x00A3,  # £ - 0x01 - POUND SIGN
        0x0024,  # $ - 0x02 - DOLLAR SIGN
        0x00A5,  # ¥ - 0x03 - YEN SIGN
        0x00E8,  # è - 0x04 - LATIN SMALL LETTER E WITH GRAVE
        0x00E9,  # é - 0x05 - LATIN SMALL LETTER E WITH ACUTE
        0x00F9,  # ù - 0x06 - LATIN SMALL LETTER U WITH GRAVE
        0x00EC,  # ì - 0x07 - LATIN SMALL LETTER I WITH GRAVE
        0x00F2,  # ò - 0x08 - LATIN SMALL LETTER O WITH GRAVE
        # According to the link up, GSM standard is ambiguous about weither
        # ç (0xE7) or Ç (0xC7) should be used at position 0x09 of GSM encoding
        # and suggest using ç. However, SFR API only accepts Ç and replaces
        # ç with c in non-unicode messages.
        0x00C7,  # Ç - 0x09 - LATIN CAPITAL LETTER C WITH CEDILLA
        0x000A,  #   - 0x0A - LINE FEED
        0x00D8,  # Ø - 0x0B - LATIN CAPITAL LETTER O WITH STROKE
        0x00F8,  # ø - 0x0C - LATIN SMALL LETTER O WITH STROKE
        0x000D,  #   - 0x0D - CARRIAGE RETURN
        0x00C5,  # Å - 0x0E - LATIN CAPITAL LETTER A WITH RING ABOVE
        0x00E5,  # å - 0x0F - LATIN SMALL LETTER A WITH RING ABOVE
        0x0394,  # Δ - 0x10 - GREEK CAPITAL LETTER DELTA
        0x005F,  # _ - 0x11 - LOW LINE
        0x03A6,  # Φ - 0x12 - GREEK CAPITAL LETTER PHI
        0x0393,  # Γ - 0x13 - GREEK CAPITAL LETTER GAMMA
        0x039B,  # Λ - 0x14 - GREEK CAPITAL LETTER LAMDA
        0x03A9,  # Ω - 0x15 - GREEK CAPITAL LETTER OMEGA
        0x03A0,  # Π - 0x16 - GREEK CAPITAL LETTER PI
        0x03A8,  # Ψ - 0x17 - GREEK CAPITAL LETTER PSI
        0x03A3,  # Σ - 0x18 - GREEK CAPITAL LETTER SIGMA
        0x0398,  # Θ - 0x19 - GREEK CAPITAL LETTER THETA
        0x039E,  # Ξ - 0x1A - GREEK CAPITAL LETTER XI
        0x00A0,  #   - 0x1B - NBSP (or escape character, see GSM 03.38 specification)
        0x00C6,  # Æ - 0x1C - LATIN CAPITAL LETTER AE
        0x00E6,  # æ - 0x1D - LATIN SMALL LETTER AE
        0x00DF,  # ß - 0x1E - LATIN SMALL LETTER SHARP S (German)
        0x00C9,  # É - 0x1F - LATIN CAPITAL LETTER E WITH ACUTE
        0x0020,  #   - 0x20 - SPACE
        0x0021,  # ! - 0x21 - EXCLAMATION MARK
        0x0022,  # " - 0x22 - QUOTATION MARK
        0x0023,  # # - 0x23 - NUMBER SIGN
        0x00A4,  # ¤ - 0x24 - CURRENCY SIGN
        0x0025,  # % - 0x25 - PERCENT SIGN
        0x0026,  # & - 0x26 - AMPERSAND
        0x0027,  # ' - 0x27 - APOSTROPHE
        0x0028,  # ( - 0x28 - LEFT PARENTHESIS
        0x0029,  # ) - 0x29 - RIGHT PARENTHESIS
        0x002A,  # * - 0x2A - ASTERISK
        0x002B,  # + - 0x2B - PLUS SIGN
        0x002C,  # , - 0x2C - COMMA
        0x002D,  # - - 0x2D - HYPHEN-MINUS
        0x002E,  # . - 0x2E - FULL STOP
        0x002F,  # / - 0x2F - SOLIDUS
        0x0030,  # 0 - 0x30 - DIGIT ZERO
        0x0031,  # 1 - 0x31 - DIGIT ONE
        0x0032,  # 2 - 0x32 - DIGIT TWO
        0x0033,  # 3 - 0x33 - DIGIT THREE
        0x0034,  # 4 - 0x34 - DIGIT FOUR
        0x0035,  # 5 - 0x35 - DIGIT FIVE
        0x0036,  # 6 - 0x36 - DIGIT SIX
        0x0037,  # 7 - 0x37 - DIGIT SEVEN
        0x0038,  # 8 - 0x38 - DIGIT EIGHT
        0x0039,  # 9 - 0x39 - DIGIT NINE
        0x003A,  # : - 0x3A - COLON
        0x003B,  # ; - 0x3B - SEMICOLON
        0x003C,  # < - 0x3C - LESS-THAN SIGN
        0x003D,  # = - 0x3D - EQUALS SIGN
        0x003E,  # > - 0x3E - GREATER-THAN SIGN
        0x003F,  # ? - 0x3F - QUESTION MARK
        0x00A1,  # ¡ - 0x40 - INVERTED EXCLAMATION MARK
        0x0041,  # A - 0x41 - LATIN CAPITAL LETTER A
        0x0042,  # B - 0x42 - LATIN CAPITAL LETTER B
        0x0043,  # C - 0x43 - LATIN CAPITAL LETTER C
        0x0044,  # D - 0x44 - LATIN CAPITAL LETTER D
        0x0045,  # E - 0x45 - LATIN CAPITAL LETTER E
        0x0046,  # F - 0x46 - LATIN CAPITAL LETTER F
        0x0047,  # G - 0x47 - LATIN CAPITAL LETTER G
        0x0048,  # H - 0x48 - LATIN CAPITAL LETTER H
        0x0049,  # I - 0x49 - LATIN CAPITAL LETTER I
        0x004A,  # J - 0x4A - LATIN CAPITAL LETTER J
        0x004B,  # K - 0x4B - LATIN CAPITAL LETTER K
        0x004C,  # L - 0x4C - LATIN CAPITAL LETTER L
        0x004D,  # M - 0x4D - LATIN CAPITAL LETTER M
        0x004E,  # N - 0x4E - LATIN CAPITAL LETTER N
        0x004F,  # O - 0x4F - LATIN CAPITAL LETTER O
        0x0050,  # P - 0x50 - LATIN CAPITAL LETTER P
        0x0051,  # Q - 0x51 - LATIN CAPITAL LETTER Q
        0x0052,  # R - 0x52 - LATIN CAPITAL LETTER R
        0x0053,  # S - 0x53 - LATIN CAPITAL LETTER S
        0x0054,  # T - 0x54 - LATIN CAPITAL LETTER T
        0x0055,  # U - 0x55 - LATIN CAPITAL LETTER U
        0x0056,  # V - 0x56 - LATIN CAPITAL LETTER V
        0x0057,  # W - 0x57 - LATIN CAPITAL LETTER W
        0x0058,  # X - 0x58 - LATIN CAPITAL LETTER X
        0x0059,  # Y - 0x59 - LATIN CAPITAL LETTER Y
        0x005A,  # Z - 0x5A - LATIN CAPITAL LETTER Z
        0x00C4,  # Ä - 0x5B - LATIN CAPITAL LETTER A WITH DIAERESIS
        0x00D6,  # Ö - 0x5C - LATIN CAPITAL LETTER O WITH DIAERESIS
        0x00D1,  # Ñ - 0x5D - LATIN CAPITAL LETTER N WITH TILDE
        0x00DC,  # Ü - 0x5E - LATIN CAPITAL LETTER U WITH DIAERESIS
        0x00A7,  # § - 0x5F - SECTION SIGN
        0x00BF,  # ¿ - 0x60 - INVERTED QUESTION MARK
        0x0061,  # a - 0x61 - LATIN SMALL LETTER A
        0x0062,  # b - 0x62 - LATIN SMALL LETTER B
        0x0063,  # c - 0x63 - LATIN SMALL LETTER C
        0x0064,  # d - 0x64 - LATIN SMALL LETTER D
        0x0065,  # e - 0x65 - LATIN SMALL LETTER E
        0x0066,  # f - 0x66 - LATIN SMALL LETTER F
        0x0067,  # g - 0x67 - LATIN SMALL LETTER G
        0x0068,  # h - 0x68 - LATIN SMALL LETTER H
        0x0069,  # i - 0x69 - LATIN SMALL LETTER I
        0x006A,  # j - 0x6A - LATIN SMALL LETTER J
        0x006B,  # k - 0x6B - LATIN SMALL LETTER K
        0x006C,  # l - 0x6C - LATIN SMALL LETTER L
        0x006D,  # m - 0x6D - LATIN SMALL LETTER M
        0x006E,  # n - 0x6E - LATIN SMALL LETTER N
        0x006F,  # o - 0x6F - LATIN SMALL LETTER O
        0x0070,  # p - 0x70 - LATIN SMALL LETTER P
        0x0071,  # q - 0x71 - LATIN SMALL LETTER Q
        0x0072,  # r - 0x72 - LATIN SMALL LETTER R
        0x0073,  # s - 0x73 - LATIN SMALL LETTER S
        0x0074,  # t - 0x74 - LATIN SMALL LETTER T
        0x0075,  # u - 0x75 - LATIN SMALL LETTER U
        0x0076,  # v - 0x76 - LATIN SMALL LETTER V
        0x0077,  # w - 0x77 - LATIN SMALL LETTER W
        0x0078,  # x - 0x78 - LATIN SMALL LETTER X
        0x0079,  # y - 0x79 - LATIN SMALL LETTER Y
        0x007A,  # z - 0x7A - LATIN SMALL LETTER Z
        0x00E4,  # ä - 0x7B - LATIN SMALL LETTER A WITH DIAERESIS
        0x00F6,  # ö - 0x7C - LATIN SMALL LETTER O WITH DIAERESIS
        0x00F1,  # ñ - 0x7D - LATIN SMALL LETTER N WITH TILDE
        0x00FC,  # ü - 0x7E - LATIN SMALL LETTER U WITH DIAERESIS
        0x00E0,  # à - 0x7F - LATIN SMALL LETTER A WITH GRAVE
        # Greek upper case letters allowed by GSM 03.38, that will be mapped
        # to their latin equivalent
        0x0391,  # Α - 0x41 - GREEK CAPITAL LETTER ALPHA
        0x0392,  # Β - 0x42 - GREEK CAPITAL LETTER BETA
        0x0395,  # Ε - 0x45 - GREEK CAPITAL LETTER EPSILON
        0x0397,  # Η - 0x48 - GREEK CAPITAL LETTER ETA
        0x0399,  # Ι - 0x49 - GREEK CAPITAL LETTER IOTA
        0x039A,  # Κ - 0x4B - GREEK CAPITAL LETTER KAPPA
        0x039C,  # Μ - 0x4D - GREEK CAPITAL LETTER MU
        0x039D,  # Ν - 0x4E - GREEK CAPITAL LETTER NU
        0x039F,  # Ο - 0x4F - GREEK CAPITAL LETTER OMICRON
        0x03A1,  # Ρ - 0x50 - GREEK CAPITAL LETTER RHO
        0x03A4,  # Τ - 0x54 - GREEK CAPITAL LETTER TAU
        0x03A7,  # Χ - 0x58 - GREEK CAPITAL LETTER CHI
        0x03A5,  # Υ - 0x59 - GREEK CAPITAL LETTER UPSILON
        0x0396,  # Ζ - 0x5A - GREEK CAPITAL LETTER ZETA
        # GSM extension escape-sequence characters
        0x000C,  # ␌ - 0x1B0A - FORM FEED
        0x005E,  # ^ - 0x1B14 - CIRCUMFLEX ACCENT
        0x007B,  # { - 0x1B28 - LEFT CURLY BRACKET
        0x007D,  # } - 0x1B29 - RIGHT CURLY BRACKET
        0x005C,  # \ - 0x1B2F - REVERSE SOLIDUS
        0x005B,  # [ - 0x1B3C - LEFT SQUARE BRACKET
        0x007E,  # ~ - 0x1B3D - TILDE
        0x005D,  # ] - 0x1B3E - RIGHT SQUARE BRACKET
        0x007C,  # | - 0x1B40 - VERTICAL LINE
        0x20AC,  # € - 0x1B65 - EURO SIGN
    ]
}


class SfrDmcGateway(SMSResource):
    service_id = models.CharField(verbose_name=_('Service ID'), max_length=15)
    service_password = models.CharField(verbose_name=_('Service Password'), max_length=15)
    space_id = models.IntegerField(verbose_name=_('Space ID'))

    TEST_DEFAULTS = {
        'create_kwargs': {
            'service_id': '1234',
            'service_password': 'krascuky',
            'space_id': '1234',
        },
        'test_vectors': [
            {
                'response': '',
                'result': {
                    'err': 1,
                    'err_desc': 'Bad JSON response',
                    'data': None,
                },
            },
            {
                'response': {'bad_json': True},
                'result': {
                    'err': 1,
                    'err_desc': 'Bad JSON response',
                    'data': None,
                },
            },
            {
                'response': {'success': False, 'no_error_detail_field': 'bad_json'},
                'result': {'err': 1, 'err_desc': 'Bad JSON response', 'data': None},
            },
            {
                'response': {'success': False, 'errorDetail': 'sfr error description'},
                'result': {
                    'err': 1,
                    'err_desc': 'sfr error description',
                    'data': None,
                },
            },
            {
                'response': {'success': True, 'response': ['1234', '5678']},
                'result': {
                    'err': 0,
                    'data': [
                        ['0033688888888', {'result': 'Envoi terminé', 'sms_id': 1234}],
                        ['0033677777777', {'result': 'Envoi terminé', 'sms_id': 1234}],
                    ],
                },
            },
        ],
    }
    URL = 'https://www.dmc.sfr-sh.fr/DmcWS/1.5.7'

    class Meta:
        verbose_name = _('SFR DMC')

    def send_msg(self, text, sender, destinations, **kwargs):
        """Send a SMS using the SFR DMC provider"""

        if len(destinations) > 20:
            raise APIError('you can\'t send more than 20 sms at once.')

        sfr_format_destinations = []
        for dest in destinations:
            sfr_format_destinations.append(f'+{dest[2:]}')  # SFR API only accepts '+' prefix

        non_standard_characters = set(text) - GSM_CHARACTERS

        if len(non_standard_characters) == 0:
            media_type = 'SMSLong'
        else:
            media_type = 'SMSUnicodeLong'

        response = self.requests.get(
            f'{self.URL}/JsonService/MessagesUnitairesWS/batchSingleCall',
            params={
                'authenticate': json.dumps(
                    {
                        'serviceId': self.service_id,
                        'servicePassword': self.service_password,
                        'spaceId': self.space_id,
                    }
                ),
                'messageUnitaires': json.dumps(
                    [
                        {'media': media_type, 'textMsg': text, 'from': sender, 'to': dest}
                        for dest in sfr_format_destinations
                    ]
                ),
            },
        )

        try:
            json_data = response.json()
        except ValueError:
            raise APIError('Bad JSON response')

        if 'success' not in json_data:
            raise APIError('Bad JSON response')
        if not json_data['success']:
            if 'errorDetail' not in json_data:
                raise APIError('Bad JSON response')
            raise APIError(json_data['errorDetail'])
