"""Per-account settings with a JSONB preferences grab-bag.

Rows are created lazily on first write. Reads of a missing row return the
caller-supplied default — no upfront row creation per account.

Settings live in `preferences` until they meet the promotion criteria in
Section 4.6 of FLOWPILOT-MIGRATION.md (hot path / validation / joins), at
which point a future migration adds a typed column and the helpers prefer it.
"""
from __future__ import annotations

import uuid
from datetime import datetime, timezone
from typing import Any, TYPE_CHECKING

from sqlalchemy import DateTime, ForeignKey, text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.dialects.postgresql import UUID, JSONB, insert as pg_insert
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.sql import select

from app.core.database import Base

if TYPE_CHECKING:
    from app.models.account import Account


class AccountSettings(Base):
    """One row per account. Created lazily on first `set_setting` call."""
    __tablename__ = "account_settings"

    account_id: Mapped[uuid.UUID] = mapped_column(
        UUID(as_uuid=True),
        ForeignKey("accounts.id", ondelete="CASCADE"),
        primary_key=True,
    )
    preferences: Mapped[dict[str, Any]] = mapped_column(
        JSONB, nullable=False, default=dict, server_default=text("'{}'::jsonb")
    )
    created_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), default=lambda: datetime.now(timezone.utc)
    )
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True),
        default=lambda: datetime.now(timezone.utc),
        onupdate=lambda: datetime.now(timezone.utc),
    )

    account: Mapped["Account"] = relationship("Account", foreign_keys=[account_id])

    @classmethod
    async def get_setting(
        cls,
        db: AsyncSession,
        account_id: uuid.UUID,
        key: str,
        default: Any = None,
    ) -> Any:
        """Return preferences[key] for the account, or `default` if no row/no key.

        Never creates a row — this is the pure-read path.
        """
        result = await db.execute(
            select(cls.preferences).where(cls.account_id == account_id)
        )
        prefs = result.scalar_one_or_none()
        if prefs is None:
            return default
        return prefs.get(key, default)

    @classmethod
    async def set_setting(
        cls,
        db: AsyncSession,
        account_id: uuid.UUID,
        key: str,
        value: Any,
    ) -> None:
        """Upsert preferences[key] = value for the account.

        Creates the row on first write; on subsequent writes, merges the key
        into the existing preferences JSON without clobbering other keys.
        Uses PostgreSQL's `||` jsonb merge operator via ON CONFLICT DO UPDATE.
        """
        stmt = pg_insert(cls).values(
            account_id=account_id,
            preferences={key: value},
        )
        stmt = stmt.on_conflict_do_update(
            index_elements=[cls.account_id],
            set_={
                # Merge the new {key: value} into the existing preferences.
                # The `||` operator on jsonb overwrites matching keys and keeps
                # all other keys intact.
                "preferences": cls.preferences.op("||")(stmt.excluded.preferences),
                "updated_at": text("now()"),
            },
        )
        await db.execute(stmt)
