"""Realism — synthetic-file state across orchestrator ticks.

The orchestrator's pre-realism file generator forgot every file the
moment it was planted: each tick wrote a brand-new ``notes-{ts}.txt``
with a literal unix-epoch suffix.  No edits, no rotation, no diurnal
shape — three of the realism failures the migration is fixing.

:class:`SyntheticFile` is the per-(decky, path) memory that lets the
realism engine read back yesterday's ``TODO.md``, mutate it, write
back the new body, and let the dashboard inspect the lineage.

Pre-v1: schema lives directly in the SQLModel; no ``_migrate_*``
helper (per the project's "no new migrations pre-v1" rule —
``feedback_no_new_migrations_prev1.md``).  Alembic lands at v1.
"""
from datetime import datetime, timezone
from typing import Any, List
from uuid import uuid4

from pydantic import BaseModel
from sqlalchemy import Column, Index, Text, UniqueConstraint
from sqlmodel import Field, SQLModel


SYNTHETIC_FILE_BODY_LIMIT = 65536
"""Cap on persisted ``synthetic_files.last_body`` bytes.

Enforced by the repo on both insert and update — callers may pass the
full body; the repo clips. Large blobs (DOCX/PDF, canary artifacts) are
wasted disk on the master side; the decky filesystem holds the canonical
bytes."""


class SyntheticFile(SQLModel, table=True):
    """One realism-planted file on one decky.

    The unique key is ``(decky_uuid, path)`` — there's at most one
    realism record per location, even if the planter has rotated the
    file (rotation updates ``edit_count`` and ``last_modified``, not
    a new row).

    ``last_body`` is capped — large blobs (DOCX/PDF, future canary
    artifacts) are truncated at write time.  The edit-in-place flow
    (stage 3b) only needs the body when the content class supports
    body-level mutation (``note``, ``todo``, ``draft``, ``script``),
    so storing the canonical bytes for binary blobs would be wasted.

    ``content_hash`` is sha256 of the *body bytes only* — never of
    metadata or wrapper headers — so a hash compare is a cheap
    "did the body change?" check across edits.
    """
    __tablename__ = "synthetic_files"
    __table_args__ = (
        UniqueConstraint(
            "decky_uuid", "path", name="uq_synthetic_files_decky_path",
        ),
        Index("ix_synthetic_files_decky_modified", "decky_uuid", "last_modified"),
    )
    uuid: str = Field(default_factory=lambda: str(uuid4()), primary_key=True)
    decky_uuid: str = Field(index=True, max_length=64)
    # Capped at 512 so the (decky_uuid, path) unique index fits MySQL's
    # 3072-byte utf8mb4 limit: (64+512)*4 = 2304 bytes. Real realism +
    # canary paths are well under (longest is
    # ``/home/<persona>/Documents/Q3-Operations-Review.docx``, ~70 chars).
    path: str = Field(max_length=512)
    persona: str = Field(max_length=128)              # EmailPersona.name
    content_class: str = Field(max_length=32, index=True)  # ContentClass enum value
    created_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc), index=True,
    )
    last_modified: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
    )
    edit_count: int = Field(default=0)
    content_hash: str = Field(max_length=64)          # sha256 hex
    last_body: str = Field(
        sa_column=Column("last_body", Text, nullable=False, default="")
    )


class SyntheticFilesResponse(BaseModel):
    total: int
    limit: int
    offset: int
    data: List[dict[str, Any]]


class RealismConfig(SQLModel, table=True):
    """Operator-tunable realism knobs.

    Single-row-per-key schema: each row carries one piece of operator
    config (today: ``key="weights"`` → JSON encoding the planner's
    user/system/canary weights and canary probability). The planner
    reads in-memory module globals; the orchestrator worker refreshes
    those globals from this table on a periodic tick.

    UUID PK + unique key per ``feedback_uuid_over_natural_keys.md``.
    """
    __tablename__ = "realism_config"
    uuid: str = Field(default_factory=lambda: str(uuid4()), primary_key=True)
    key: str = Field(max_length=64, unique=True, index=True)
    value: str = Field(
        sa_column=Column("value", Text, nullable=False, default="{}"),
    )
    updated_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
    )
