"""Punchlists: the product row and its kit instance, created as ONE act.

The kit instance is what grants/visibility hang off; the product row carries
what the kit has no opinion about (client_party_id, external_ref, state). They
share the id and are created in the same service path — the caddie-plan rule
that makes the kit's invite/grant screens always have an instance to grant on.
"""

import re
from uuid import uuid4

from sqlalchemy import select

from app import accounts
from app import bw_accounts as bwa
from app.models import Punchlist, PunchlistSeen, utcnow

_NON_SLUG = re.compile(r"[^a-z0-9]+")


class PunchlistError(Exception):
    def __init__(self, message, code="BAD_INPUT"):
        super().__init__(message)
        self.code = code


def _slugify(title: str) -> str:
    slug = _NON_SLUG.sub("-", (title or "").lower()).strip("-")[:64].strip("-")
    return slug or f"punchlist-{uuid4().hex[:8]}"


def create(session, actor: str, title: str, *, client_label: str = "",
           external_ref: str | None = None) -> Punchlist:
    """Kit instance + product row together. Kit enforces `instances.create`."""
    title = (title or "").strip()
    if not title:
        raise PunchlistError("A punchlist needs a title.", "BAD_INPUT")

    base = _slugify(title)
    pid = None
    for candidate in [base] + [f"{base[:60].rstrip('-')}-{n}" for n in range(2, 50)]:
        try:
            bwa.create_instance(actor, candidate, title, audit=accounts.audit)
            pid = candidate
            break
        except bwa.AccountsError as exc:
            if exc.code == "EXISTS":
                continue
            raise
    if pid is None:
        raise PunchlistError("Could not find a free id for that title.", "EXISTS")

    row = Punchlist(id=pid, title=title, client_label=client_label,
                    external_ref=external_ref, created_by=actor)
    session.add(row)
    session.flush()
    return row


def get(session, punchlist_id: str) -> Punchlist | None:
    return session.get(Punchlist, punchlist_id)


def visible_to(session, username: str) -> list[Punchlist]:
    """Product rows for the kit instances this person may see, kit-ordered."""
    ids = [i["id"] for i in accounts.visible_instances(username)]
    if not ids:
        return []
    rows = session.execute(select(Punchlist).where(Punchlist.id.in_(ids))).scalars().all()
    by_id = {r.id: r for r in rows}
    return [by_id[i] for i in ids if i in by_id]


def mark_seen(session, punchlist_id: str, username: str) -> None:
    row = session.get(PunchlistSeen, (punchlist_id, username))
    if row is None:
        session.add(PunchlistSeen(punchlist_id=punchlist_id, username=username,
                                  last_seen_at=utcnow()))
    else:
        row.last_seen_at = utcnow()
    session.flush()


def last_seen(session, punchlist_id: str, username: str):
    row = session.get(PunchlistSeen, (punchlist_id, username))
    return row.last_seen_at if row else None
