import math
from datetime import timedelta

from django.conf import settings
from django.db import models, transaction
from django.utils import timezone


class RegistrationSequence(models.Model):
    """Tracks the last registration number issued per year (GT-<year>-NNNN)."""
    year = models.PositiveIntegerField(unique=True)
    last_number = models.PositiveIntegerField(default=0)

    def __str__(self):
        return f'{self.year}: {self.last_number}'


def generate_registration_number():
    """Atomically reserves the next registration number for the current year.

    Uses select_for_update so concurrent entries from different agents/posts
    never collide on the same number.
    """
    year = timezone.now().year
    with transaction.atomic():
        seq, _ = RegistrationSequence.objects.select_for_update().get_or_create(
            year=year, defaults={'last_number': 0}
        )
        seq.last_number += 1
        seq.save(update_fields=['last_number'])
        return f'GT-{year}-{seq.last_number:04d}'


class TruckMovement(models.Model):
    FREE_DAYS = 3  # délai de franchise accordé au camion
    FREE_HOURS = FREE_DAYS * 24  # 72 h
    PENALTY_PER_HOUR = 500  # FCFA par heure entamée au-delà du délai

    class Status(models.TextChoices):
        IN_PARK = 'in_park', 'In Park'
        EXITED = 'exited', 'Exited'

    registration_number = models.CharField(max_length=20, unique=True, editable=False, blank=True)
    truck_number = models.CharField(max_length=50, verbose_name='Truck number / plate')
    t_number = models.CharField(max_length=50, verbose_name='T number (transit)')
    t_date = models.DateField(verbose_name='T date')

    entry_datetime = models.DateTimeField(
        default=timezone.now,
        verbose_name="Date et heure d'entrée",
        help_text="Saisie manuellement par l'agent lors de l'enregistrement.",
    )
    exit_datetime = models.DateTimeField(null=True, blank=True)

    photo = models.ImageField(upload_to='truck_photos/%Y/%m/', null=True, blank=True)
    cargo_description = models.CharField(
        max_length=100,
        blank=True,
        null=True,
        verbose_name='Marchandise',
        help_text='Description du bien transporté par le camion.',
    )
    cargo_quantity = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        null=True,
        blank=True,
        verbose_name='Quantité chargée',
        help_text='Quantité chargée. Indiquez la valeur sans unité si unité séparée est fournie.',
    )
    cargo_unit = models.CharField(
        max_length=20,
        blank=True,
        null=True,
        verbose_name='Unité de mesure',
        help_text='Unité de la quantité transportée, par ex. t, kg, pièces, m3.',
    )
    missing_quantity = models.DecimalField(
        max_digits=10,
        decimal_places=2,
        null=True,
        blank=True,
        verbose_name='Quantité manquante',
        help_text='Quantité manquante dans la même unité que la quantité chargée.',
    )

    status = models.CharField(max_length=10, choices=Status.choices, default=Status.IN_PARK)
    penalty_amount = models.PositiveIntegerField(default=0)

    created_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
        related_name='truck_entries_created',
    )
    exited_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, on_delete=models.PROTECT,
        null=True, blank=True, related_name='truck_exits_recorded',
    )

    class Meta:
        ordering = ['-entry_datetime']

    def __str__(self):
        return self.registration_number

    def save(self, *args, **kwargs):
        if not self.registration_number:
            self.registration_number = generate_registration_number()
        super().save(*args, **kwargs)

    def calculate_penalty(self, at=None):
        """Penalty in FCFA for the elapsed time between entry and `at`.

        `at` defaults to exit_datetime for a truck that has left, and to "now"
        for one still in the park, so a truck sitting past its free delay
        accrues a penalty before it ever exits.
        """
        end = at or self.exit_datetime or timezone.now()
        if not self.entry_datetime:
            return 0
        duration = end - self.entry_datetime
        free_delta = timedelta(hours=self.FREE_HOURS)
        if duration <= free_delta:
            return 0
        overage_hours = math.ceil((duration - free_delta).total_seconds() / 3600)
        return overage_hours * self.PENALTY_PER_HOUR

    @property
    def current_penalty(self):
        """Penalty as of right now — live for trucks still in the park,
        frozen at the recorded amount once the truck has exited."""
        if self.status == self.Status.EXITED:
            return self.penalty_amount
        return self.calculate_penalty()

    def register_exit(self, exited_by):
        self.exit_datetime = timezone.now()
        self.exited_by = exited_by
        self.penalty_amount = self.calculate_penalty(at=self.exit_datetime)
        self.status = self.Status.EXITED
        self.save()
