"""
SMS, through whichever provider this install is configured for.

Twilio cannot deliver to Iranian networks, and SMS.ir cannot send outside
Iran, so an install uses one or the other and never both. SMS_PROVIDER picks
it; credentials come from CMS Settings, never from .env.

The two providers differ in a way that matters: Twilio takes free text, while
SMS.ir sends one-time codes through a template approved in advance and filled
by named parameters. That is why send_otp exists separately from send_sms
rather than every caller composing its own message.
"""
import logging
import requests
from django.conf import settings

log = logging.getLogger(__name__)

SMSIR_BASE = 'https://api.sms.ir/v1'


def _provider():
    return (getattr(settings, 'SMS_PROVIDER', '') or 'twilio').strip().lower()


def _site():
    try:
        from apps.pages.models import SiteSettings
        from apps.tenants.models import Tenant
        return SiteSettings.load(Tenant.objects.filter(is_master=True).first())
    except Exception:
        return None


def _credentials():
    """Twilio credentials from Settings. Never .env."""
    account_sid = auth_token = sms_from = ''
    site = _site()
    if site:
        account_sid = site.twilio_account_sid or ''
        auth_token  = site.twilio_auth_token or ''
        sms_from    = getattr(site, 'twilio_sms_from', '') or ''
    return {
        'account_sid': account_sid,
        'auth_token':  auth_token,
        'sms_from':    sms_from,
    }


def _smsir_credentials():
    """SMS.ir credentials from Settings. Never .env."""
    api_key = line = template = ''
    site = _site()
    if site:
        api_key  = getattr(site, 'smsir_api_key', '') or ''
        line     = getattr(site, 'smsir_line_number', '') or ''
        template = getattr(site, 'smsir_otp_template', '') or ''
    return {'api_key': api_key, 'line': line, 'template': template}


def _iran_local(number: str) -> str:
    """SMS.ir wants 09xxxxxxxxx, not E.164.

    Numbers reach us in whatever shape the user typed, and the API rejects a
    leading plus outright rather than normalising it.
    """
    n = (number or '').strip().replace(' ', '').replace('-', '')
    for prefix in ('+98', '0098', '98'):
        if n.startswith(prefix):
            n = n[len(prefix):]
            break
    if not n.startswith('0'):
        n = '0' + n
    return n


# ─── Twilio ──────────────────────────────────────────────────────────────────

def _twilio_send(to: str, message: str) -> bool:
    creds = _credentials()
    if not creds['account_sid'] or not creds['auth_token'] or not creds['sms_from']:
        log.warning('Twilio SMS credentials not configured — message skipped.')
        return False
    try:
        resp = requests.post(
            f"https://api.twilio.com/2010-04-01/Accounts/{creds['account_sid']}/Messages.json",
            auth=(creds['account_sid'], creds['auth_token']),
            data={'From': creds['sms_from'], 'To': to, 'Body': message},
            timeout=10,
        )
        if resp.status_code in (200, 201):
            return True
        log.error('Twilio SMS error: HTTP %s — %s', resp.status_code, resp.text)
        return False
    except Exception as exc:
        log.error('Twilio SMS exception: %s', exc)
        return False


# ─── SMS.ir ──────────────────────────────────────────────────────────────────

def _smsir_verify(to: str, code: str) -> bool:
    """One-time code through the approved template.

    The template holds the wording; we supply only the code. Parameter names
    have to match the ones registered with SMS.ir, and CODE is their own
    convention for the verification placeholder.
    """
    creds = _smsir_credentials()
    if not creds['api_key'] or not creds['template']:
        log.warning('SMS.ir key or template not configured — OTP skipped.')
        return False
    try:
        resp = requests.post(
            f'{SMSIR_BASE}/send/verify',
            headers={
                'Content-Type': 'application/json',
                'Accept': 'text/plain',
                'x-api-key': creds['api_key'],
            },
            json={
                'mobile': _iran_local(to),
                'templateId': int(creds['template']) if str(creds['template']).isdigit() else creds['template'],
                'parameters': [{'name': 'CODE', 'value': str(code)}],
            },
            timeout=15,
        )
        body = resp.json() if resp.content else {}
        if resp.status_code == 200 and body.get('status') == 1:
            return True
        log.error('SMS.ir verify error: HTTP %s — %s', resp.status_code, resp.text[:300])
        return False
    except Exception as exc:
        log.error('SMS.ir verify exception: %s', exc)
        return False


def _smsir_bulk(to: str, message: str) -> bool:
    """An ordinary message, which needs the sending line rather than a template."""
    creds = _smsir_credentials()
    if not creds['api_key'] or not creds['line']:
        log.warning('SMS.ir key or line number not configured — message skipped.')
        return False
    try:
        resp = requests.post(
            f'{SMSIR_BASE}/send/bulk',
            headers={
                'Content-Type': 'application/json',
                'Accept': 'text/plain',
                'x-api-key': creds['api_key'],
            },
            json={
                'lineNumber': creds['line'],
                'messageText': message,
                'mobiles': [_iran_local(to)],
            },
            timeout=15,
        )
        body = resp.json() if resp.content else {}
        if resp.status_code == 200 and body.get('status') == 1:
            return True
        log.error('SMS.ir bulk error: HTTP %s — %s', resp.status_code, resp.text[:300])
        return False
    except Exception as exc:
        log.error('SMS.ir bulk exception: %s', exc)
        return False


# ─── Public ──────────────────────────────────────────────────────────────────

def send_sms(to: str, message: str) -> bool:
    """Send an ordinary message through whichever provider is configured."""
    if _provider() == 'smsir':
        return _smsir_bulk(to, message)
    return _twilio_send(to, message)


def send_otp(to: str, code: str, message: str = '') -> bool:
    """Send a one-time code.

    Kept apart from send_sms because SMS.ir will not carry a code in free
    text: it has to go through the registered template, and those messages
    also jump the queue and reach numbers that have blocked marketing SMS.
    """
    if _provider() == 'smsir':
        return _smsir_verify(to, code)
    return _twilio_send(to, message or f'Your code: {code}')
