import calendar
from datetime import date
from decimal import Decimal

from django.db import transaction
from django.utils import timezone

from core.choices import BillingCycle, ServiceStatus
from operations.models import InternalTask

from .models import Payment, Renewal


CYCLE_MONTHS = {
    BillingCycle.MONTHLY: 1,
    BillingCycle.QUARTERLY: 3,
    BillingCycle.SEMIANNUAL: 6,
    BillingCycle.ANNUAL: 12,
    BillingCycle.BIENNIAL: 24,
}


def add_months(value: date, months: int) -> date:
    month_index = value.month - 1 + months
    year = value.year + month_index // 12
    month = month_index % 12 + 1
    day = min(value.day, calendar.monthrange(year, month)[1])
    return date(year, month, day)


def next_cycle_date(value: date, billing_cycle: str) -> date:
    months = CYCLE_MONTHS.get(billing_cycle)
    if not months:
        return value
    return add_months(value, months)


@transaction.atomic
def create_renewal_for_service(service, due_date=None) -> tuple[Renewal, bool]:
    if not service.is_recurring:
        raise ValueError("O serviço não está marcado como recorrente.")
    due_date = due_date or service.next_renewal_date
    if not due_date:
        raise ValueError("O serviço não tem data de renovação definida.")

    renewal, created = Renewal.objects.get_or_create(
        service=service,
        due_date=due_date,
        defaults={
            "period_start": service.start_date,
            "period_end": due_date,
            "cost_amount": service.cost_price,
            "sale_amount": Decimal("0.00") if service.is_complimentary else service.sale_price,
            "status": Renewal.Status.TO_NOTIFY,
        },
    )
    if created:
        InternalTask.objects.get_or_create(
            renewal=renewal,
            task_type=InternalTask.TaskType.RENEW_SERVICE,
            defaults={
                "title": f"Preparar renovação — {service.name}",
                "client": service.client,
                "service": service,
                "priority": InternalTask.Priority.HIGH if renewal.days_until_due <= 15 else InternalTask.Priority.NORMAL,
                "due_date": due_date,
                "description": "Confirmar pagamento e concluir a renovação do serviço.",
            },
        )
    return renewal, created


@transaction.atomic
def mark_payment_as_paid(payment: Payment, paid_at=None) -> Payment:
    paid_at = paid_at or timezone.now()
    payment.status = Payment.Status.PAID
    payment.paid_at = paid_at
    payment.save(update_fields=["status", "paid_at", "updated_at"])

    renewal = payment.renewal
    if renewal.outstanding_amount <= Decimal("0.00"):
        renewal.status = Renewal.Status.PAID
        renewal.paid_at = paid_at
    elif renewal.amount_paid > Decimal("0.00"):
        renewal.status = Renewal.Status.PARTIAL
    renewal.save(update_fields=["status", "paid_at", "updated_at"])

    InternalTask.objects.filter(
        renewal=renewal,
        task_type=InternalTask.TaskType.CONFIRM_PAYMENT,
        status__in=[InternalTask.Status.TODO, InternalTask.Status.IN_PROGRESS, InternalTask.Status.WAITING],
    ).update(status=InternalTask.Status.DONE, completed_at=paid_at)

    # Prepara a confirmação, sem enviar automaticamente.
    try:
        from notifications.models import NotificationTemplate
        from notifications.workflows import create_notification_from_template
        create_notification_from_template(renewal, NotificationTemplate.Type.PAYMENT_RECEIVED, automatic=True, scheduled_for=timezone.now())
    except ValueError:
        pass
    return payment


@transaction.atomic
def mark_renewal_as_renewed(renewal: Renewal, renewed_until=None, *, allow_unpaid=False) -> Renewal:
    service = renewal.service
    if renewal.status == Renewal.Status.CANCELLED:
        raise ValueError("Uma renovação cancelada não pode ser concluída.")
    if service.status in {ServiceStatus.CANCELLED, ServiceStatus.SUSPENDED, ServiceStatus.ARCHIVED}:
        raise ValueError("O serviço não está num estado que permita renovação.")
    exempt_from_payment = service.is_complimentary or service.customer_pays_provider_directly
    if not allow_unpaid and not exempt_from_payment and renewal.outstanding_amount > Decimal("0.00"):
        raise ValueError(f"Ainda faltam receber {renewal.outstanding_amount:.2f} € antes de concluir a renovação.")

    renewed_until = renewed_until or next_cycle_date(renewal.due_date, service.billing_cycle)
    if not renewed_until or renewed_until <= renewal.due_date:
        raise ValueError("A nova data de renovação deve ser posterior à data atual do serviço.")
    renewal.status = Renewal.Status.RENEWED
    renewal.renewed_until = renewed_until
    renewal.save(update_fields=["status", "renewed_until", "updated_at"])

    service.next_renewal_date = renewed_until
    service.status = ServiceStatus.ACTIVE
    service.save(update_fields=["next_renewal_date", "status", "updated_at"])

    InternalTask.objects.filter(
        renewal=renewal,
        task_type=InternalTask.TaskType.RENEW_SERVICE,
        status__in=[InternalTask.Status.TODO, InternalTask.Status.IN_PROGRESS, InternalTask.Status.WAITING],
    ).update(status=InternalTask.Status.DONE, completed_at=timezone.now())

    # Prepara a confirmação, mantendo o envio manual nesta versão.
    try:
        from notifications.models import NotificationTemplate
        from notifications.workflows import create_notification_from_template
        create_notification_from_template(renewal, NotificationTemplate.Type.RENEWAL_CONFIRMED, automatic=True, scheduled_for=timezone.now())
    except ValueError:
        pass
    return renewal
