"""Per-user Synapse reaction settings + instruction version history (postgres).

Reaction instructions are the contract between the user and the agent: the
dispatcher meta-pass and every reaction run read them before acting. The agent
itself may edit the document (self-improvement) — every edit lands in
synapse_instruction_versions with an `edited_by` label, so the history doubles
as the audit log of the agent touching its own rules.
"""

from datetime import datetime, timezone
from typing import Literal

from pydantic import BaseModel

PushTarget = Literal["app", "app_synapse", "synapse"]
CompletionNotify = Literal["always", "important", "never"]

_VERSIONS_KEEP = 100  # versions kept per user; older ones are trimmed


class SynapseSettings(BaseModel):
    user_id: str
    reactions_enabled: bool = False
    push_target: PushTarget = "app"
    completion_notify: CompletionNotify = "important"
    instructions: str = ""
    updated_at: datetime | None = None


class InstructionVersion(BaseModel):
    content: str
    edited_by: str
    reason: str | None = None
    created_at: datetime


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

    async def get(self, user_id: str) -> SynapseSettings:
        row = await self._pool.fetchrow(
            "SELECT user_id, reactions_enabled, push_target, completion_notify, instructions, updated_at "
            "FROM synapse_settings WHERE user_id = $1",
            user_id,
        )
        if row is None:
            return SynapseSettings(user_id=user_id)
        return SynapseSettings(
            user_id=row["user_id"],
            reactions_enabled=row["reactions_enabled"],
            push_target=row["push_target"],
            completion_notify=row["completion_notify"],
            instructions=row["instructions"],
            updated_at=row["updated_at"],
        )

    async def save(self, settings: SynapseSettings) -> None:
        now = datetime.now(timezone.utc)
        await self._pool.execute(
            """
            INSERT INTO synapse_settings
                (user_id, reactions_enabled, push_target, completion_notify, instructions, updated_at)
            VALUES ($1, $2, $3, $4, $5, $6)
            ON CONFLICT (user_id) DO UPDATE SET
                reactions_enabled = EXCLUDED.reactions_enabled,
                push_target = EXCLUDED.push_target,
                completion_notify = EXCLUDED.completion_notify,
                instructions = EXCLUDED.instructions,
                updated_at = EXCLUDED.updated_at
            """,
            settings.user_id, settings.reactions_enabled, settings.push_target,
            settings.completion_notify, settings.instructions, now,
        )

    async def save_instructions(
        self, user_id: str, content: str, edited_by: Literal["user", "navi"],
        reason: str | None = None,
    ) -> None:
        """Update the instructions document and record the edit in the history.

        One transaction: the version row and the settings row never disagree.
        """
        now = datetime.now(timezone.utc)
        async with self._pool.acquire() as conn, conn.transaction():
            await conn.execute(
                """
                INSERT INTO synapse_settings (user_id, instructions, updated_at)
                VALUES ($1, $2, $3)
                ON CONFLICT (user_id) DO UPDATE SET
                    instructions = EXCLUDED.instructions,
                    updated_at = EXCLUDED.updated_at
                """,
                user_id, content, now,
            )
            await conn.execute(
                """
                INSERT INTO synapse_instruction_versions (user_id, content, edited_by, reason, created_at)
                VALUES ($1, $2, $3, $4, $5)
                """,
                user_id, content, edited_by, reason, now,
            )
            await conn.execute(
                """
                DELETE FROM synapse_instruction_versions
                WHERE user_id = $1 AND id NOT IN (
                    SELECT id FROM synapse_instruction_versions
                    WHERE user_id = $1 ORDER BY created_at DESC, id DESC LIMIT $2
                )
                """,
                user_id, _VERSIONS_KEEP,
            )

    async def list_versions(self, user_id: str, limit: int = 20) -> list[InstructionVersion]:
        rows = await self._pool.fetch(
            "SELECT content, edited_by, reason, created_at "
            "FROM synapse_instruction_versions WHERE user_id = $1 "
            "ORDER BY created_at DESC, id DESC LIMIT $2",
            user_id, limit,
        )
        return [InstructionVersion(**r) for r in rows]