"""Private local bootstrap grants; never put these credentials in discovery."""
from __future__ import annotations

import hashlib
import secrets
import threading
import time

_PURPOSE_CAPABILITIES = {
    'interactive': frozenset({'session:create', 'session:read', 'session:submit',
                              'session:control', 'session:approve', 'session:respond'}),
    'native-http': frozenset({'http:owner'}),
    # Same-user host operation, not permission to read or mutate another profile.
    'native-profile-create': frozenset({'profiles:create'}),
    # Rename/delete of the ONE profile it is minted for (#140345): a registry write on that home,
    # never a read of it or any other route.
    'native-profile-registry': frozenset({'profiles:registry'}),
    'exposure': frozenset({'transport:delegate'}),
    'worker-adoption': frozenset({'worker:adopt'}),
}
# A grant's profile reach: its own home, or (interactive only) every served sibling.
_SCOPES = frozenset({'profile', 'host'})


class TicketStore:
    TTL_SECONDS = 30
    MAX_ENTRIES = 1024

    def __init__(self, instance_id: str, profile_ids: frozenset[str]):
        if not instance_id or not profile_ids:
            raise ValueError('bootstrap requires instance and served profiles')
        self.instance_id = instance_id
        self.profile_ids = frozenset(profile_ids)
        self._entries: dict[str, tuple[float, dict]] = {}
        self._lock = threading.Lock()

    def mint(self, *, profile_id, subject, purpose, scope='profile') -> str:
        """``scope='host'`` lets an interactive connection select every profile this process
        serves (the Desktop's one shared socket); the default binds exactly ``profile_id``."""
        if (profile_id not in self.profile_ids or purpose not in _PURPOSE_CAPABILITIES or not subject
                or scope not in _SCOPES or (scope == 'host' and purpose != 'interactive')):
            raise PermissionError('bootstrap binding rejected')
        with self._lock:
            now = time.monotonic()
            self._entries = {k: v for k, v in self._entries.items() if v[0] > now}
            if len(self._entries) >= self.MAX_ENTRIES:
                raise PermissionError('bootstrap capacity exhausted')
            ticket = secrets.token_urlsafe(32)
            self._entries[hashlib.sha256(ticket.encode()).hexdigest()] = (
                now + self.TTL_SECONDS,
                {'instance_id': self.instance_id, 'profile_id': profile_id, 'subject': subject,
                 'purpose': purpose, 'capabilities': _PURPOSE_CAPABILITIES[purpose], 'scope': scope})
            return ticket

    def redeem(self, ticket, *, profile_id, purpose) -> dict:
        """``profile_id=None`` accepts a ticket minted for ANY served profile; the returned grant's
        ``profile_id`` then selects the authority. A named profile must match exactly."""
        if not isinstance(ticket, str) or len(ticket) > 256:
            raise PermissionError('invalid bootstrap ticket')
        key = hashlib.sha256(ticket.encode()).hexdigest()
        with self._lock:
            entry = self._entries.get(key)
            if entry is None:
                raise PermissionError('invalid bootstrap ticket')
            expires, grant = entry
            if expires <= time.monotonic():
                del self._entries[key]
                raise PermissionError('expired bootstrap ticket')
            served = grant['profile_id'] in self.profile_ids if profile_id is None else grant['profile_id'] == profile_id
            if not served or grant['purpose'] != purpose:
                raise PermissionError('bootstrap binding rejected')
            del self._entries[key]
            return dict(grant)

    def outstanding(self) -> bool:
        """An unexpired ticket was minted and not yet redeemed: a client is attaching right now."""
        with self._lock:
            now = time.monotonic()
            return any(expires > now for expires, _grant in self._entries.values())

    def revoke(self) -> None:
        with self._lock:
            self._entries.clear()
