from decimal import Decimal

from django.core.exceptions import ValidationError
from django.db import models
from django.db.models import Sum
from django.utils import timezone

from core.choices import PaymentMethod
from core.models import TimeStampedModel


class Renewal(TimeStampedModel):
    class Status(models.TextChoices):
        TO_NOTIFY = "to_notify", "Por avisar"
        NOTIFIED = "notified", "Avisado"
        WAITING_PAYMENT = "waiting_payment", "A aguardar pagamento"
        PARTIAL = "partial", "Pagamento parcial"
        PAID = "paid", "Pago"
        PROCESSING = "processing", "Renovação em processamento"
        RENEWED = "renewed", "Renovado"
        OVERDUE = "overdue", "Vencido"
        CANCELLED = "cancelled", "Cancelado"

    service = models.ForeignKey("services.Service", on_delete=models.PROTECT, related_name="renewals", verbose_name="Serviço")
    due_date = models.DateField("Data de vencimento", db_index=True)
    period_start = models.DateField("Início do período", null=True, blank=True)
    period_end = models.DateField("Fim do período", null=True, blank=True)
    cost_amount = models.DecimalField("Custo", max_digits=10, decimal_places=2, default=Decimal("0.00"))
    sale_amount = models.DecimalField("Valor a cobrar", max_digits=10, decimal_places=2, default=Decimal("0.00"))
    status = models.CharField("Estado", max_length=25, choices=Status.choices, default=Status.TO_NOTIFY, db_index=True)
    notice_30_sent_at = models.DateTimeField("Aviso 30 dias", null=True, blank=True)
    notice_15_sent_at = models.DateTimeField("Aviso 15 dias", null=True, blank=True)
    notice_7_sent_at = models.DateTimeField("Aviso 7 dias", null=True, blank=True)
    due_notice_sent_at = models.DateTimeField("Aviso no vencimento", null=True, blank=True)
    overdue_notice_sent_at = models.DateTimeField("Aviso de vencido", null=True, blank=True)
    paid_at = models.DateTimeField("Pago em", null=True, blank=True)
    renewed_until = models.DateField("Renovado até", null=True, blank=True)
    notes = models.TextField("Observações", blank=True)

    class Meta:
        verbose_name = "Renovação"
        verbose_name_plural = "Renovações"
        ordering = ["due_date", "service__client__name"]
        constraints = [models.UniqueConstraint(fields=["service", "due_date"], name="unique_service_due_date")]
        indexes = [models.Index(fields=["status", "due_date"])]

    def clean(self):
        if self.period_start and self.period_end and self.period_start > self.period_end:
            raise ValidationError({"period_end": "O fim do período deve ser posterior ao início."})
        if self.renewed_until and self.renewed_until < self.due_date:
            raise ValidationError({"renewed_until": "A nova data deve ser posterior ou igual à data de vencimento."})

    @property
    def client(self):
        return self.service.client

    @property
    def days_until_due(self):
        return (self.due_date - timezone.localdate()).days

    @property
    def amount_paid(self):
        return self.payments.filter(status=Payment.Status.PAID).aggregate(total=Sum("amount"))["total"] or Decimal("0.00")

    @property
    def outstanding_amount(self):
        return max(self.sale_amount - self.amount_paid, Decimal("0.00"))

    def __str__(self):
        return f"{self.service} — {self.due_date:%d/%m/%Y}"


class Payment(TimeStampedModel):
    class Status(models.TextChoices):
        PENDING = "pending", "Pendente"
        WAITING_PROOF = "waiting_proof", "A aguardar comprovativo"
        VALIDATING = "validating", "Em validação"
        PARTIAL = "partial", "Parcial"
        PAID = "paid", "Pago"
        OVERDUE = "overdue", "Em atraso"
        REJECTED = "rejected", "Rejeitado"
        CANCELLED = "cancelled", "Cancelado"
        REFUNDED = "refunded", "Devolvido"

    renewal = models.ForeignKey(Renewal, on_delete=models.PROTECT, related_name="payments", verbose_name="Renovação")
    amount = models.DecimalField("Valor", max_digits=10, decimal_places=2)
    method = models.CharField("Método", max_length=30, choices=PaymentMethod.choices)
    status = models.CharField("Estado", max_length=25, choices=Status.choices, default=Status.PENDING, db_index=True)
    requested_at = models.DateTimeField("Solicitado em", default=timezone.now)
    paid_at = models.DateTimeField("Pago em", null=True, blank=True)
    proof = models.FileField("Comprovativo", upload_to="payment_proofs/%Y/%m/", blank=True)
    reference = models.CharField("Referência interna", max_length=120, blank=True)
    invoice_number = models.CharField("Fatura / recibo", max_length=120, blank=True)
    notes = models.TextField("Observações", blank=True)

    class Meta:
        verbose_name = "Pagamento"
        verbose_name_plural = "Pagamentos"
        ordering = ["-requested_at"]
        indexes = [models.Index(fields=["status", "paid_at"])]

    def clean(self):
        if self.status == self.Status.PAID and not self.paid_at:
            raise ValidationError({"paid_at": "Indique a data do pagamento quando o estado é Pago."})

    @property
    def client(self):
        return self.renewal.service.client

    def __str__(self):
        return f"{self.client} — {self.amount:.2f} €"
