"""Orchestrator-emitted activity events.

Purpose-built sibling to ``logs.Log`` so attacker-originated events stay
cleanly separable from synthetic life-injection events at query time.
The orchestrator worker is the sole writer.
"""
from datetime import datetime, timezone
from typing import Any, List, Optional
from uuid import uuid4

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


class OrchestratorEvent(SQLModel, table=True):
    """One orchestrator-driven action against a decky.

    ``kind`` discriminates the two MVP flavours:

    * ``"traffic"`` — a protocol-driven interaction (SSH command exec for
      MVP). ``src_decky_uuid`` is the *logical* originator and may differ
      from the actual TCP source for the duration of the MVP, where the
      orchestrator process drives the connection from the host. ``v1``
      will execute the connection from inside the source container.
    * ``"file"``  — a filesystem touch via ``docker exec`` against the
      destination decky. ``src_decky_uuid`` is null.

    ``payload`` is the per-action JSON envelope: command run, exit code,
    stdout/stderr digest, file path, byte counts, etc. Schema is
    deliberately loose — the worker can extend it without a migration.
    """
    __tablename__ = "orchestrator_events"
    __table_args__ = (
        Index("ix_orchestrator_events_dst_ts", "dst_decky_uuid", "ts"),
    )
    uuid: str = Field(default_factory=lambda: str(uuid4()), primary_key=True)
    ts: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc), index=True
    )
    kind: str = Field(index=True, max_length=16)              # traffic|file
    protocol: str = Field(index=True, max_length=16)          # ssh for MVP
    action: str = Field(max_length=64)                         # exec:uptime|file:create|...
    # No FK to topology_deckies: dst/src may be a TopologyDecky.uuid
    # (MazeNET source), a "host_uuid:name" composite (fleet / SWARM shard
    # sources), or — for retired deckies — a row that's already gone.  The
    # column is an opaque identifier matching whatever
    # ``BaseRepository.list_running_deckies`` emits in its ``uuid`` field.
    # Index is kept; the FK was misleading and broke fleet-source events.
    src_decky_uuid: Optional[str] = Field(default=None, index=True)
    dst_decky_uuid: str = Field(index=True)
    success: bool = Field(default=False, index=True)
    payload: str = Field(
        sa_column=Column("payload", Text, nullable=False, default="{}")
    )


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


class OrchestratorEmail(SQLModel, table=True):
    """One fake email generated by the ``decnet emailgen`` worker.

    Sibling table to :class:`OrchestratorEvent` — kept disjoint because
    email rows carry domain-specific fields (subject, message_id,
    in_reply_to, language) that have no analogue in the SSH/file events
    and would otherwise bloat ``OrchestratorEvent.payload``.

    The mail decky's UUID lives in ``mail_decky_uuid`` (the host serving
    the IMAP/POP3 mailbox).  ``thread_id`` is a worker-side UUID used to
    chain replies; ``in_reply_to`` is the parent email's RFC 2822
    Message-ID header value (or ``None`` for thread roots).

    ``payload`` follows the same loose-JSON convention as
    :class:`OrchestratorEvent`: ``bytes``, ``generation_ms``, ``model``,
    ``mannerisms_used``, etc.  The worker can extend it without a
    migration.
    """
    __tablename__ = "orchestrator_emails"
    __table_args__ = (
        Index("ix_orchestrator_emails_mail_ts", "mail_decky_uuid", "ts"),
        Index("ix_orchestrator_emails_thread", "thread_id"),
    )
    uuid: str = Field(default_factory=lambda: str(uuid4()), primary_key=True)
    ts: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc), index=True
    )
    mail_decky_uuid: str = Field(index=True)
    thread_id: str = Field(index=True)
    message_id: str = Field(max_length=255)
    in_reply_to: Optional[str] = Field(default=None, max_length=255)
    sender_email: str = Field(max_length=255, index=True)
    recipient_email: str = Field(max_length=255, index=True)
    subject: str = Field(max_length=512)
    language: str = Field(max_length=8, default="en")
    eml_path: str = Field(max_length=1024)
    success: bool = Field(default=False, index=True)
    payload: str = Field(
        sa_column=Column("payload", Text, nullable=False, default="{}")
    )


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