"""SEQUENCE() preprocessing.

Substitutes SEQUENCE() calls in a rendered expression with atomically
incremented padded counter values backed by a Mongo ``counters``
collection. Supports year-scoped counter keys with optional custom keys
and an app-scoped filter.

Supported patterns:
    SEQUENCE()
    SEQUENCE(6)
    SEQUENCE(6, 999999)
    SEQUENCE("invoice")
    SEQUENCE(6, "invoice")
    SEQUENCE(6, 999999, "invoice")
    SEQUENCE(key="invoice")
    SEQUENCE(6, key="invoice")
    SEQUENCE(6, 999999, key="invoice")
"""
import re
from datetime import UTC, datetime
from typing import Optional

from motor.motor_asyncio import AsyncIOMotorDatabase


_SEQUENCE_PATTERN = (
    r"SEQUENCE\s*\(\s*"
    r"(?:(\d+)\s*)?"
    r"(?:,\s*(\d+)\s*)?"
    r'(?:,?\s*(?:key\s*=\s*)?["\']([^"\']+)["\']\s*)?'
    r"\)"
)


async def _get_next_sequence(
    db: AsyncIOMotorDatabase,
    entity_id: Optional[str],
    app_id: Optional[str],
    padding: int = 6,
    max_limit: int = 999999,
    custom_key: Optional[str] = None,
) -> str:
    """Atomic increment against the ``counters`` collection."""
    year = datetime.now(UTC).strftime("%Y")
    if custom_key:
        counter_key = f"{custom_key}_{year}"
    elif entity_id:
        counter_key = f"{entity_id}_{year}"
    else:
        counter_key = f"global_{year}"

    filter_query = {"key": counter_key}
    if app_id:
        filter_query["app_id"] = app_id

    set_fields = {
        "seq": {
            "$cond": {
                "if": {"$gte": ["$seq", max_limit]},
                "then": 1,
                "else": {"$add": [{"$ifNull": ["$seq", 0]}, 1]},
            }
        },
        "created_at": {"$ifNull": ["$created_at", datetime.now(UTC)]},
        "year": year,
    }
    if app_id:
        set_fields["app_id"] = app_id
    if entity_id:
        set_fields["entity_id"] = entity_id
    if custom_key:
        set_fields["custom_key"] = custom_key

    result = await db["counters"].find_one_and_update(
        filter_query, [{"$set": set_fields}], upsert=True, return_document=True
    )

    return str(result.get("seq", 1)).zfill(padding)


async def preprocess_sequence_calls(
    db: AsyncIOMotorDatabase,
    expr: str,
    entity_id: Optional[str],
    app_id: Optional[str],
) -> str:
    """Substitute every SEQUENCE(...) call in the expression with a quoted
    padded counter value, in document order."""
    matches = list(re.finditer(_SEQUENCE_PATTERN, expr))
    if not matches:
        return expr

    result = expr
    for match in reversed(matches):
        padding = int(match.group(1)) if match.group(1) else 6
        max_limit = int(match.group(2)) if match.group(2) else 999999
        custom_key = match.group(3)

        seq = await _get_next_sequence(
            db=db,
            entity_id=entity_id,
            app_id=app_id,
            padding=padding,
            max_limit=max_limit,
            custom_key=custom_key,
        )
        result = result[: match.start()] + f'"{seq}"' + result[match.end() :]

    return result
