from __future__ import annotations

from datetime import timedelta

from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.db.models import Q
from django.utils import timezone

from core.models import TimeStampedModel


class WebsiteMonitor(TimeStampedModel):
    class HttpMethod(models.TextChoices):
        GET = "GET", "GET"
        HEAD = "HEAD", "HEAD"

    class Status(models.TextChoices):
        PENDING = "pending", "Por verificar"
        UP = "up", "Operacional"
        DEGRADED = "degraded", "Desempenho degradado"
        DOWN = "down", "Indisponível"
        MAINTENANCE = "maintenance", "Em manutenção"
        PAUSED = "paused", "Pausado"

    INTERVAL_CHOICES = (
        (1, "A cada minuto"),
        (5, "A cada 5 minutos"),
        (10, "A cada 10 minutos"),
        (15, "A cada 15 minutos"),
        (30, "A cada 30 minutos"),
        (60, "A cada hora"),
        (360, "A cada 6 horas"),
        (1440, "Diariamente"),
    )

    technical_asset = models.ForeignKey(
        "technical.TechnicalAsset",
        on_delete=models.CASCADE,
        related_name="website_monitors",
        verbose_name="Ativo técnico",
    )
    name = models.CharField("Nome do monitor", max_length=180)
    url = models.URLField("URL a verificar", max_length=500)
    is_active = models.BooleanField("Monitorização ativa", default=True, db_index=True)
    client_visible = models.BooleanField("Visível no portal do cliente", default=True)

    http_method = models.CharField("Método HTTP", max_length=8, choices=HttpMethod.choices, default=HttpMethod.GET)
    interval_minutes = models.PositiveIntegerField("Intervalo", choices=INTERVAL_CHOICES, default=5)
    timeout_seconds = models.PositiveSmallIntegerField(
        "Timeout (segundos)",
        default=15,
        validators=[MinValueValidator(1), MaxValueValidator(60)],
    )
    expected_status_codes = models.CharField(
        "Códigos HTTP esperados",
        max_length=100,
        default="200-399",
        help_text="Ex.: 200,204,301 ou 200-399.",
    )
    expected_text = models.CharField(
        "Texto obrigatório na resposta",
        max_length=250,
        blank=True,
        help_text="Opcional. A verificação falha se o texto não estiver no conteúdo recebido.",
    )
    follow_redirects = models.BooleanField("Seguir redirecionamentos", default=True)
    verify_ssl = models.BooleanField("Validar ligação SSL", default=True)
    inspect_ssl_certificate = models.BooleanField("Analisar certificado SSL", default=True)
    ssl_warning_days = models.PositiveSmallIntegerField("Avisar SSL a expirar (dias)", default=30)
    warning_response_ms = models.PositiveIntegerField("Avisar resposta lenta (ms)", default=2500)
    allow_private_networks = models.BooleanField(
        "Permitir rede privada",
        default=False,
        help_text="Ative apenas para monitorizar serviços internos autorizados.",
    )

    alert_after_failures = models.PositiveSmallIntegerField(
        "Abrir incidente após falhas",
        default=2,
        validators=[MinValueValidator(1), MaxValueValidator(20)],
    )
    recover_after_successes = models.PositiveSmallIntegerField(
        "Resolver após sucessos",
        default=1,
        validators=[MinValueValidator(1), MaxValueValidator(20)],
    )
    create_incident = models.BooleanField("Criar incidente automaticamente", default=True)
    create_task = models.BooleanField("Criar tarefa interna", default=True)
    notify_internal = models.BooleanField("Notificar equipa", default=True)
    notify_client = models.BooleanField("Notificar cliente", default=False)

    status = models.CharField("Estado atual", max_length=20, choices=Status.choices, default=Status.PENDING, db_index=True)
    maintenance_until = models.DateTimeField("Manutenção até", null=True, blank=True, db_index=True)
    last_checked_at = models.DateTimeField("Última verificação", null=True, blank=True, db_index=True)
    next_check_at = models.DateTimeField("Próxima verificação", null=True, blank=True, db_index=True)
    last_success_at = models.DateTimeField("Último sucesso", null=True, blank=True)
    last_failure_at = models.DateTimeField("Última falha", null=True, blank=True)
    last_response_ms = models.PositiveIntegerField("Último tempo (ms)", null=True, blank=True)
    last_http_status = models.PositiveSmallIntegerField("Último HTTP", null=True, blank=True)
    last_error = models.TextField("Último erro", blank=True)
    consecutive_failures = models.PositiveSmallIntegerField("Falhas consecutivas", default=0)
    consecutive_successes = models.PositiveSmallIntegerField("Sucessos consecutivos", default=0)

    class Meta:
        verbose_name = "Monitor de website"
        verbose_name_plural = "Monitores de websites"
        ordering = ["technical_asset__service__client__name", "name"]
        constraints = [
            models.UniqueConstraint(fields=["technical_asset", "url"], name="uniq_monitor_url_per_asset"),
        ]
        indexes = [
            models.Index(fields=["is_active", "next_check_at"]),
            models.Index(fields=["status", "last_checked_at"]),
        ]

    def clean(self):
        if self.technical_asset_id:
            from technical.models import TechnicalAsset

            if self.technical_asset.asset_type != TechnicalAsset.AssetType.WEBSITE:
                raise ValidationError({"technical_asset": "A monitorização deve estar ligada a um ativo técnico do tipo Website."})
        if not self.url.lower().startswith(("http://", "https://")):
            raise ValidationError({"url": "Utilize um endereço HTTP ou HTTPS válido."})
        from .services import parse_expected_status_codes

        try:
            parse_expected_status_codes(self.expected_status_codes)
        except ValueError as exc:
            raise ValidationError({"expected_status_codes": str(exc)}) from exc
        if self.maintenance_until and self.maintenance_until <= timezone.now():
            self.maintenance_until = None

    def save(self, *args, **kwargs):
        if not self.name and self.technical_asset_id:
            self.name = self.technical_asset.label
        if not self.is_active:
            self.status = self.Status.PAUSED
        elif self.maintenance_until and self.maintenance_until > timezone.now():
            self.status = self.Status.MAINTENANCE
        if self.is_active and not self.next_check_at:
            self.next_check_at = timezone.now()
        super().save(*args, **kwargs)

    @property
    def is_due(self):
        if not self.is_active:
            return False
        now = timezone.now()
        if self.maintenance_until and self.maintenance_until > now:
            return False
        return not self.next_check_at or self.next_check_at <= now

    @property
    def current_incident(self):
        return self.incidents.filter(
            status__in=[MonitorIncident.Status.OPEN, MonitorIncident.Status.ACKNOWLEDGED]
        ).order_by("-opened_at").first()

    def uptime_percentage(self, *, days=30):
        since = timezone.now() - timedelta(days=days)
        checks = self.checks.filter(checked_at__gte=since).exclude(result=MonitorCheck.Result.SKIPPED)
        total = checks.count()
        if not total:
            return None
        successful = checks.filter(result__in=[MonitorCheck.Result.UP, MonitorCheck.Result.DEGRADED]).count()
        return round((successful / total) * 100, 2)

    def __str__(self):
        return self.name


class MonitorCheck(models.Model):
    class Result(models.TextChoices):
        UP = "up", "Operacional"
        DEGRADED = "degraded", "Degradado"
        DOWN = "down", "Falhou"
        MAINTENANCE = "maintenance", "Manutenção"
        SKIPPED = "skipped", "Ignorado"

    class ErrorType(models.TextChoices):
        NONE = "", "Sem erro"
        TIMEOUT = "timeout", "Timeout"
        DNS = "dns", "DNS"
        CONNECTION = "connection", "Ligação"
        SSL = "ssl", "SSL"
        HTTP = "http", "Código HTTP"
        CONTENT = "content", "Conteúdo"
        SECURITY = "security", "Segurança"
        OTHER = "other", "Outro"

    monitor = models.ForeignKey(WebsiteMonitor, on_delete=models.CASCADE, related_name="checks", verbose_name="Monitor")
    checked_at = models.DateTimeField("Verificado em", default=timezone.now, db_index=True)
    result = models.CharField("Resultado", max_length=20, choices=Result.choices, db_index=True)
    http_status = models.PositiveSmallIntegerField("Código HTTP", null=True, blank=True)
    response_time_ms = models.PositiveIntegerField("Tempo de resposta (ms)", null=True, blank=True)
    final_url = models.URLField("URL final", max_length=700, blank=True)
    redirect_count = models.PositiveSmallIntegerField("Redirecionamentos", default=0)
    resolved_ip = models.GenericIPAddressField("IP resolvido", null=True, blank=True)
    response_size_bytes = models.PositiveIntegerField("Tamanho da resposta", null=True, blank=True)
    content_match = models.BooleanField("Conteúdo validado", null=True, blank=True)
    ssl_valid = models.BooleanField("SSL válido", null=True, blank=True)
    ssl_issuer = models.CharField("Emissor SSL", max_length=250, blank=True)
    ssl_expires_at = models.DateTimeField("SSL expira em", null=True, blank=True)
    ssl_days_remaining = models.IntegerField("Dias de SSL", null=True, blank=True)
    error_type = models.CharField("Tipo de erro", max_length=20, choices=ErrorType.choices, blank=True)
    error_message = models.TextField("Erro", blank=True)
    details = models.JSONField("Detalhes técnicos", default=dict, blank=True)

    class Meta:
        verbose_name = "Verificação de website"
        verbose_name_plural = "Verificações de websites"
        ordering = ["-checked_at"]
        indexes = [
            models.Index(fields=["monitor", "-checked_at"]),
            models.Index(fields=["result", "-checked_at"]),
        ]

    def __str__(self):
        return f"{self.monitor} — {self.checked_at:%d/%m/%Y %H:%M}"


class MonitorIncident(TimeStampedModel):
    class Status(models.TextChoices):
        OPEN = "open", "Aberto"
        ACKNOWLEDGED = "acknowledged", "Reconhecido"
        RESOLVED = "resolved", "Resolvido"
        IGNORED = "ignored", "Ignorado"

    class Severity(models.TextChoices):
        WARNING = "warning", "Aviso"
        CRITICAL = "critical", "Crítico"

    monitor = models.ForeignKey(WebsiteMonitor, on_delete=models.CASCADE, related_name="incidents", verbose_name="Monitor")
    title = models.CharField("Título", max_length=220)
    reason = models.TextField("Motivo")
    severity = models.CharField("Gravidade", max_length=20, choices=Severity.choices, default=Severity.CRITICAL, db_index=True)
    status = models.CharField("Estado", max_length=20, choices=Status.choices, default=Status.OPEN, db_index=True)
    opened_at = models.DateTimeField("Aberto em", default=timezone.now, db_index=True)
    acknowledged_at = models.DateTimeField("Reconhecido em", null=True, blank=True)
    acknowledged_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="acknowledged_monitor_incidents",
        verbose_name="Reconhecido por",
    )
    resolved_at = models.DateTimeField("Resolvido em", null=True, blank=True, db_index=True)
    resolved_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="resolved_monitor_incidents",
        verbose_name="Resolvido por",
    )
    first_check = models.ForeignKey(
        MonitorCheck,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="incidents_started",
        verbose_name="Primeira verificação",
    )
    last_check = models.ForeignKey(
        MonitorCheck,
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="incidents_updated",
        verbose_name="Última verificação",
    )
    failure_count = models.PositiveIntegerField("Falhas registadas", default=1)
    internal_task = models.ForeignKey(
        "operations.InternalTask",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
        related_name="monitoring_incidents",
        verbose_name="Tarefa interna",
    )
    client_visible = models.BooleanField("Visível no portal do cliente", default=True)
    resolution_note = models.TextField("Nota de resolução", blank=True)

    class Meta:
        verbose_name = "Incidente de monitorização"
        verbose_name_plural = "Incidentes de monitorização"
        ordering = ["status", "-opened_at"]
        constraints = [
            models.UniqueConstraint(
                fields=["monitor"],
                condition=Q(status__in=["open", "acknowledged"]),
                name="uniq_open_monitor_incident",
            )
        ]
        indexes = [
            models.Index(fields=["status", "severity", "-opened_at"]),
        ]

    @property
    def duration_seconds(self):
        end = self.resolved_at or timezone.now()
        return max(0, int((end - self.opened_at).total_seconds()))

    def acknowledge(self, *, user=None):
        if self.status == self.Status.OPEN:
            self.status = self.Status.ACKNOWLEDGED
            self.acknowledged_at = timezone.now()
            self.acknowledged_by = user
            self.save(update_fields=["status", "acknowledged_at", "acknowledged_by", "updated_at"])

    def resolve(self, *, user=None, note=""):
        self.status = self.Status.RESOLVED
        self.resolved_at = timezone.now()
        self.resolved_by = user
        if note:
            self.resolution_note = note
        self.save(update_fields=["status", "resolved_at", "resolved_by", "resolution_note", "updated_at"])

    def __str__(self):
        return self.title
