from datetime import datetime, date, time
from dataclasses import dataclass
from django.db import transaction
from django.db.models import F
from django.utils import timezone
from django.apps import apps

from accrete.tenant import get_tenant


@dataclass
class SequenceData:

    counter: int
    number: str


def get_nextval(
    name: str, 
    per_date: datetime | date = None, 
    create_values: dict = None
) -> SequenceData:
    Sequence = apps.get_model('sequence', 'Sequence')
    tenant = get_tenant()
    sequence = Sequence.objects.filter(tenant_id=tenant.pk, name=name).first()
    if not sequence:
        create_values = create_values or {}
        create_values.update(name=name, tenant_id=tenant.pk)
        sequence = Sequence(**create_values)
        sequence.save()
    if isinstance(per_date, date):
        per_date = datetime.combine(per_date, time.min)
    per_date = per_date or timezone.now()
    year = per_date.year
    if sequence.has_subsequence_per_year:
        name = f'{name}__{year}'
        sequence = Sequence.objects.get_or_create(
            defaults={
                'name': name,
                'has_subsequence_per_year': False,
                'step': sequence.step,
                'prefix': sequence.prefix,
                'padding': sequence.padding,
            },
            name=name,
            tenant_id=tenant.pk,
        )[0]
    counter = _get_nextval(name)
    placeholder = {
        'Y': year,
        'y': str(year)[2:4],
        'm': str(per_date.month).zfill(2),
        'd': str(per_date.day).zfill(2),
        'H': per_date.hour,
        'M': per_date.minute,
        'S': per_date.second,
    }
    number = sequence.prefix % placeholder
    number += str(counter).zfill(sequence.padding)
    return SequenceData(
        counter=counter,
        number=number,
    )


def _get_nextval(name: str, create_values: dict = None) -> int:
    Sequence = apps.get_model('sequence', 'Sequence')
    tenant = get_tenant()
    with transaction.atomic():
        seq = Sequence.objects.filter(
            tenant_id=tenant.pk, name=name
        ).select_for_update().first()

        if seq is None and create_values is None:
            raise ValueError(f'Sequence "{name}" does not exist.')
        elif seq is None:
            create_values = create_values or {}
            create_values.update(name=name, tenant_id=tenant.pk)
            seq = Sequence(**create_values)
            seq.save()

        nextval = seq.nextval
        seq.nextval = F('nextval') + seq.step
        seq.save()

    return nextval
