"""
Reachability check for every active site, including Final Target itself.

Run it on a schedule, every five minutes is a sensible default:

    */5 * * * * cd /home/finaltarget/public_html/backend && \
        venv/bin/python manage.py ping_sites --settings=config.settings.production

Only records a sample, never changes tenant state. Safe to run as often as you
like, though each run makes one HTTPS request per site.
"""
import time

import requests
from django.core.management.base import BaseCommand
from django.utils import timezone

from apps.tenants.models import Tenant
from apps.ftlink.models import UptimeSample, Incident
from apps.ftlink.diagnostics import diagnose
from apps.ftlink.alerts import send_incident_alert

TIMEOUT = 10
UA = 'FinalTarget-UptimeCheck/1.0'


class Command(BaseCommand):
    help = 'Check every active site is reachable and record the result.'

    def add_arguments(self, parser):
        parser.add_argument('--slug', help='Check one tenant only')
        parser.add_argument('--prune-days', type=int, default=90,
                            help='Delete samples older than this. 0 keeps everything.')
        parser.add_argument('--no-alerts', action='store_true',
                            help='Record incidents without emailing or texting anyone.')

    def handle(self, *args, **options):
        from apps.ftlink.models import JobRun
        # Sites with monitoring switched off are skipped, so an unlaunched
        # domain cannot raise incidents nobody needs to act on.
        qs = Tenant.objects.filter(is_active=True, monitor_enabled=True).exclude(primary_domain='')
        if options.get('slug'):
            qs = qs.filter(slug=options['slug'])

        checked = 0
        for tenant in qs:
            domain = (tenant.primary_domain or '').strip().rstrip('/')
            if not domain:
                continue
            url = domain if domain.startswith('http') else f'https://{domain}'

            ok, code, latency, error = False, None, None, ''
            started = time.monotonic()
            try:
                r = requests.get(url, timeout=TIMEOUT, allow_redirects=True,
                                 headers={'User-Agent': UA})
                latency = int((time.monotonic() - started) * 1000)
                code = r.status_code
                ok = r.status_code < 400
                if not ok:
                    error = f'HTTP {r.status_code}'
            except requests.RequestException as exc:
                latency = int((time.monotonic() - started) * 1000)
                error = str(exc)[:200]

            UptimeSample.objects.create(
                tenant=tenant, ok=ok, status_code=code,
                latency_ms=latency, error=error,
            )
            checked += 1

            # Incident tracking. One incident spans a continuous run of
            # failures, so a site down for an hour is one entry, not twelve.
            open_incident = Incident.objects.filter(tenant=tenant, ended_at__isnull=True).first()
            if not ok:
                if open_incident:
                    open_incident.fail_count += 1
                    open_incident.save(update_fields=['fail_count'])
                else:
                    cause, diagnosis, fix = diagnose(url, error, code)
                    incident = Incident.objects.create(
                        tenant=tenant, started_at=timezone.now(),
                        cause=cause, status_code=code, error=error[:250],
                        diagnosis=diagnosis, suggested_fix=fix,
                    )
                    self.stdout.write(self.style.WARNING(f'     incident opened: {cause}. {fix}'))
                    if not options.get('no_alerts'):
                        sent = send_incident_alert(incident)
                        self.stdout.write(f'     alerted: {sent}')
            elif open_incident:
                open_incident.ended_at = timezone.now()
                open_incident.save(update_fields=['ended_at'])
                self.stdout.write(self.style.SUCCESS(f'     incident closed after {open_incident.fail_count} failed checks'))
                if not options.get('no_alerts'):
                    sent = send_incident_alert(open_incident, resolved=True)
                    self.stdout.write(f'     recovery notice: {sent}')
            self.stdout.write(
                f'{"UP  " if ok else "DOWN"} {tenant.name:<24} {code or "-":<5} {latency or "-"}ms {error}'
            )

        days = options.get('prune_days') or 0
        if days:
            cutoff = timezone.now() - timezone.timedelta(days=days)
            removed, _ = UptimeSample.objects.filter(checked_at__lt=cutoff).delete()
            if removed:
                self.stdout.write(f'pruned {removed} samples older than {days} days')

        self.stdout.write(self.style.SUCCESS(f'checked {checked} site(s)'))
        JobRun.record('ping_sites', note=f'{checked} sites checked')
