# 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 datetime
import logging
import urllib.parse

import requests
from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.core.mail import send_mail
from django.db import models
from django.template.loader import render_to_string
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

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


class SMSFactorSMSGateway(SMSResource):
    auth_token = models.CharField(verbose_name=_('Auth Token'), max_length=255)
    credit_threshold_alert = models.PositiveIntegerField(
        verbose_name=_('Credit alert threshold'), default=500
    )
    credit_left = models.PositiveIntegerField(verbose_name=_('Credit left'), default=0, editable=False)
    alert_emails = ArrayField(
        models.EmailField(blank=True),
        blank=True,
        null=True,
        verbose_name=_('Email addresses list to send credit alerts to, separated by comma'),
    )
    credit_alert_timestamp = models.DateTimeField(null=True, editable=False)

    # unecessary field
    allow_premium_rate = None

    class Meta:
        verbose_name = 'SMS Factor'
        db_table = 'sms_factor'

    TEST_DEFAULTS = {
        'create_kwargs': {
            'auth_token': 'yyy',
            'credit_threshold_alert': 1000,
        },
        'test_vectors': [
            {
                'status_code': 200,
                'response': {
                    "status": -7,
                    "message": "Erreur de données",
                    "details": "Texte du message introuvable",
                },
                'result': {
                    'err': 1,
                    'err_desc': 'SMS Factor error: some destinations failed',
                    'data': [
                        ['33688888888', "Texte du message introuvable"],
                        ['33677777777', "Texte du message introuvable"],
                    ],
                },
            },
            {
                'status_code': 200,
                'response': {
                    "status": 1,
                    "message": "OK",
                    "ticket": "14672468",
                    "cost": 2,
                    "credits": 642,
                    "total": 2,
                    "sent": 2,
                    "blacklisted": 0,
                    "duplicated": 0,
                    "invalid": 0,
                    "npai": 0,
                },
                'result': {
                    'err': 0,
                    'data': {
                        "status": 1,
                        "message": "OK",
                        "ticket": "14672468",
                        "cost": 2,
                        "credits": 642,
                        "total": 2,
                        "sent": 2,
                        "blacklisted": 0,
                        "duplicated": 0,
                        "invalid": 0,
                        "npai": 0,
                    },
                },
            },
        ],
    }
    URL = 'https://api.smsfactor.com'

    def request(self, method, endpoint, **kwargs):
        url = urllib.parse.urljoin(self.URL, endpoint)

        headers = {
            "Authorization": f"Bearer {self.auth_token}",
            "Accept": "application/json",
        }

        try:
            response = self.requests.request(method, url, headers=headers, **kwargs)
        except requests.RequestException as e:
            raise APIError('SMS Factor: request failed, %s' % e)
        else:
            try:
                result = response.json()
            except ValueError:
                raise APIError('SMS Factor: bad JSON response')
        try:
            response.raise_for_status()
        except requests.RequestException as e:
            raise APIError('SMS Factor: %s "%s"' % (e, result))
        return result

    def send_msg(self, text, sender, destinations, **kwargs):
        """Send a SMS using the SMS Factor provider"""
        # from https://dev.smsfactor.com/en/api/sms/send/send-single
        # and https://dev.smsfactor.com/en/api/sms/send/send-simulate
        # set destinations phone number in E.164 format (without the + prefix)
        # [country code][phone number including area code]
        destinations = [dest[2:] for dest in destinations]

        results = []

        for dest in destinations:
            params = {
                'sender': sender,
                'text': text,
                'to': dest,
                'pushtype': 'alert' if not kwargs.get('stop') else 'marketing',
            }
            data = self.request('get', 'send', params=params)
            logging.info('SMS Factor answered with %s', data)
            results.append(data)

        errors = [f'SMS Factor error: {r["status"]}: {r["message"]}' for r in results if r['status'] != 1]

        consumed_credits = None
        try:
            self.credit_left = results[-1]['credits']
            consumed_credits = sum(r['cost'] for r in results)
        except KeyError:
            # no credits key, there was probably an error with the request
            pass
        else:
            self.save(update_fields=['credit_left'])
        if any(errors):
            raise APIError('SMS Factor error: some destinations failed', data=errors)
        return consumed_credits

    def update_credit_left(self):
        result = self.request('get', endpoint='credits')
        try:
            # SMS Factor returns this as a string, for an unknown reason
            self.credit_left = int(result['credits'])
        except KeyError:
            self.logger.warning('Cannot retrieve credits for sms-factor connector: %s', result)
        else:
            self.save(update_fields=['credit_left'])

    def send_credit_alert_if_needed(self):
        if self.credit_left >= self.credit_threshold_alert:
            return
        if self.credit_alert_timestamp and self.credit_alert_timestamp > timezone.now() - datetime.timedelta(
            days=1
        ):
            return  # alerts are sent daily
        ctx = {
            'connector': self,
            'connector_url': urllib.parse.urljoin(settings.SITE_BASE_URL, self.get_absolute_url()),
        }
        subject = render_to_string('smsfactor/credit_alert_subject.txt', ctx).strip()
        body = render_to_string('smsfactor/credit_alert_body.txt', ctx)
        html_body = render_to_string('smsfactor/credit_alert_body.html', ctx)
        send_mail(
            subject,
            body,
            settings.DEFAULT_FROM_EMAIL,
            self.alert_emails,
            html_message=html_body,
        )
        self.credit_alert_timestamp = timezone.now()
        self.save()
        self.logger.warning('credit is too low, alerts were sent to %s', self.alert_emails)

    def hourly(self):
        super().hourly()
        self.update_credit_left()
        self.send_credit_alert_if_needed()
