"""Sequence service — get_next_number with SELECT FOR UPDATE locking, CRUD, delete."""

from __future__ import annotations

import uuid
from datetime import UTC, datetime
from typing import Any

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.audit import log_audit
from app.models.sequence import Sequence


def _sequence_to_dict(s: Sequence) -> dict[str, Any]:
    """Serialize a Sequence ORM object to dict."""
    return {
        "id": str(s.id),
        "name": s.name,
        "prefix": s.prefix,
        "next_number": s.next_number,
        "padding": s.padding,
        "created_at": s.created_at.isoformat() if s.created_at else None,
        "updated_at": s.updated_at.isoformat() if s.updated_at else None,
    }


async def get_next_number(
    db: AsyncSession,
    name: str,
    tenant_id: uuid.UUID,
) -> str:
    """Get the next formatted number for a sequence.

    Uses SELECT FOR UPDATE to lock the row, increment next_number,
    and return the formatted string (prefix + zero-padded number).

    Raises ValueError if the sequence does not exist.
    """
    q = (
        select(Sequence)
        .where(
            Sequence.name == name,
            Sequence.tenant_id == tenant_id,
            Sequence.deleted_at.is_(None),
        )
        .with_for_update()
    )
    result = await db.execute(q)
    seq = result.scalar_one_or_none()
    if seq is None:
        raise ValueError(f"Sequence '{name}' not found for tenant {tenant_id}")

    current_number = seq.next_number
    formatted = f"{seq.prefix}{str(current_number).zfill(seq.padding)}"
    seq.next_number = current_number + 1

    await db.flush()
    return formatted


async def create_sequence(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    data: dict[str, Any],
) -> dict[str, Any]:
    """Create a new sequence."""
    sequence = Sequence(
        tenant_id=tenant_id,
        name=data["name"],
        prefix=data.get("prefix", ""),
        next_number=1,
        padding=data.get("padding", 4),
    )
    db.add(sequence)
    await db.flush()
    await db.refresh(sequence)
    await log_audit(
        db, tenant_id, user_id, "create", "sequence", sequence.id,
        changes={"name": sequence.name, "prefix": sequence.prefix},
    )
    return _sequence_to_dict(sequence)


async def list_sequences(
    db: AsyncSession,
    tenant_id: uuid.UUID,
) -> dict[str, Any]:
    """List all sequences for a tenant."""
    q = select(Sequence).where(
        Sequence.tenant_id == tenant_id,
        Sequence.deleted_at.is_(None),
    ).order_by(Sequence.name.asc())
    result = await db.execute(q)
    sequences = result.scalars().all()
    return {
        "items": [_sequence_to_dict(s) for s in sequences],
        "total": len(sequences),
    }


async def update_sequence(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    sequence_id: uuid.UUID,
    data: dict[str, Any],
) -> dict[str, Any] | None:
    """Update a sequence (name, prefix, padding only — NOT next_number)."""
    q = select(Sequence).where(
        Sequence.id == sequence_id,
        Sequence.tenant_id == tenant_id,
        Sequence.deleted_at.is_(None),
    )
    result = await db.execute(q)
    sequence = result.scalar_one_or_none()
    if sequence is None:
        return None

    changes: dict[str, Any] = {}
    for field in ("name", "prefix", "padding"):
        if field in data and data[field] is not None:
            old_val = getattr(sequence, field)
            changes[field] = {"old": old_val, "new": data[field]}
            setattr(sequence, field, data[field])

    await db.flush()
    await db.refresh(sequence)
    await log_audit(db, tenant_id, user_id, "update", "sequence", sequence_id, changes=changes)
    return _sequence_to_dict(sequence)


async def delete_sequence(
    db: AsyncSession,
    tenant_id: uuid.UUID,
    user_id: uuid.UUID,
    sequence_id: uuid.UUID,
) -> bool:
    """Soft-delete a sequence."""
    q = select(Sequence).where(
        Sequence.id == sequence_id,
        Sequence.tenant_id == tenant_id,
        Sequence.deleted_at.is_(None),
    )
    result = await db.execute(q)
    sequence = result.scalar_one_or_none()
    if sequence is None:
        return False

    sequence.deleted_at = datetime.now(UTC)
    await db.flush()
    await log_audit(
        db, tenant_id, user_id, "delete", "sequence", sequence_id,
        changes={"name": sequence.name},
    )
    return True
