"""
Every reference this install issues, built in one place.

Ticket numbers, order numbers, chat references and project briefs all used one
hardcoded literal. Five files, five copies, and every one of them shipped in
the template, so a client site issued another company's references to its own
customers.

The shape
---------

    PREFIX  T  YYMM  NNNN        FTT260800012
    ^^^^^^  ^  ^^^^  ^^^^
    install |  when  running number, four digits, restarts each month
            type

    Invoice   PREFIX - YYMM NNNN     FT-26080001

One letter tells you what you are looking at before you have opened anything,
which matters when a customer quotes a number down the phone:

    T  ticket        O  order        C  chat        B  project brief

The invoice is the exception and keeps its hyphen. It has no type letter
because the hyphen is its marker, and because invoice references go on
documents people keep.

Old references are never rewritten. Anything already issued as FT-123456 stays
FT-123456 and still resolves.
"""

TICKET  = 'T'
ORDER   = 'O'
CHAT    = 'C'
BRIEF   = 'B'


def prefix_for(tenant=None):
    """
    Letters for this install's references. Never blank.

    Called without a tenant it uses the master row, which is correct for
    management commands and anything running outside a request. If there is
    no tenant at all, which happens on a half-installed site, it returns REF
    rather than raising, because failing to issue a ticket number is worse
    than issuing a dull one.
    """
    try:
        if tenant is None or getattr(tenant, 'pk', 0) == 0:
            from apps.tenants.models import Tenant
            tenant = Tenant.objects.filter(is_master=True).first()
        if tenant is not None:
            return tenant.prefix
    except Exception:
        pass
    return 'REF'


def stem(kind, tenant=None, when=None):
    """
    Everything before the running number: prefix, type letter, year and month.

    Useful on its own for finding the highest number issued so far in the
    current month, which is how the sequence is worked out.
    """
    from django.utils import timezone
    when = when or timezone.now()
    return f'{prefix_for(tenant)}{kind}{when:%y%m}'


def build(kind, tenant=None, when=None, seq=1):
    """One complete reference."""
    return f'{stem(kind, tenant, when)}{seq:04d}'


def next_for(model, field, kind, tenant=None, when=None):
    """
    The next reference for a model, counting from the highest already issued
    this month.

    Counted per tenant, so one busy site does not push another site's numbers
    along. Anything that does not end in four digits is ignored rather than
    crashing the save, so a hand-typed reference in the table cannot stop new
    ones being created.

    Then checked globally, which is the part that matters. ticket_number,
    order_number and invoice_number are all unique across the whole table, not
    per tenant, and two installs are allowed to land on the same letters,
    which happens more easily than it sounds. Without this loop the
    second site to raise a ticket in a given month hits an IntegrityError and
    the customer's message is lost. Choosing distinct prefixes is still the
    right fix, but losing a support request is not an acceptable way to find
    out that two of them clash.
    """
    from django.db.models import Max
    head = stem(kind, tenant, when)
    qs = model.objects.filter(**{f'{field}__startswith': head})
    if tenant is not None:
        try:
            qs = qs.filter(tenant=tenant)
        except Exception:
            pass
    last = qs.aggregate(Max(field))[f'{field}__max']
    seq = 1
    if last:
        try:
            seq = int(last[len(head):]) + 1
        except (ValueError, TypeError):
            seq = qs.count() + 1

    taken = set(
        model.objects.filter(**{f'{field}__startswith': head})
                     .values_list(field, flat=True)
    )
    candidate = f'{head}{seq:04d}'
    while candidate in taken:
        seq += 1
        candidate = f'{head}{seq:04d}'
    return candidate
