"""Storage for per-user Synapse s2s delivery secrets (postgres, asyncpg).

The secret is verified on every delivery, so unlike api_tokens it cannot be
stored as a hash — it is kept Fernet-encrypted (see navi.auth.encrypt) and is
shown in the UI exactly once, on creation.
"""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime, timezone

from navi.auth.encrypt import get_encryptor


@dataclass
class SynapseTarget:
    id: int
    user_id: str
    token_ref: str
    created_at: datetime
    revoked_at: datetime | None = None


_CREATE = """
INSERT INTO synapse_targets (user_id, token_ref, secret_enc, created_at)
VALUES ($1, $2, $3, $4)
RETURNING id, created_at
"""

_LIST_FOR_USER = """
SELECT id, user_id, token_ref, created_at, revoked_at
FROM synapse_targets WHERE user_id = $1 AND revoked_at IS NULL
ORDER BY created_at DESC
"""

_REVOKE = """
UPDATE synapse_targets SET revoked_at = $2 WHERE id = $1 AND user_id = $3 AND revoked_at IS NULL
"""

# For the gateway: every active secret, decrypted, with its owner.
_ALL_ACTIVE_SECRETS = """
SELECT t.id, t.user_id, t.token_ref, t.secret_enc
FROM synapse_targets t
WHERE t.revoked_at IS NULL
"""


class SynapseTargetStore:
    def __init__(self, pool):
        self._pool = pool

    async def create(self, user_id: str, token_ref: str, secret: str) -> SynapseTarget:
        row = await self._pool.fetchrow(
            _CREATE, user_id, token_ref, get_encryptor().encrypt(secret),
            datetime.now(timezone.utc),
        )
        return SynapseTarget(
            id=row["id"], user_id=user_id, token_ref=token_ref, created_at=row["created_at"],
        )

    async def list_for_user(self, user_id: str) -> list[SynapseTarget]:
        rows = await self._pool.fetch(_LIST_FOR_USER, user_id)
        return [
            SynapseTarget(
                id=r["id"], user_id=r["user_id"], token_ref=r["token_ref"],
                created_at=r["created_at"], revoked_at=r["revoked_at"],
            )
            for r in rows
        ]

    async def revoke(self, target_id: int, user_id: str) -> bool:
        result = await self._pool.fetchval(
            _REVOKE, target_id, datetime.now(timezone.utc), user_id,
        )
        return result is not None

    async def active_secrets(self) -> list[dict]:
        """[(id, user_id, token_ref, secret_plain)] — ordered by creation."""
        rows = await self._pool.fetch(_ALL_ACTIVE_SECRETS)
        decrypt = get_encryptor().decrypt
        return [
            {
                "id": r["id"],
                "user_id": r["user_id"],
                "token_ref": r["token_ref"],
                "secret": decrypt(r["secret_enc"]),
            }
            for r in rows
        ]