"""History / audit / AI-pipeline models.

CARRIED from v1 near-verbatim (see data/v1-snapshot/v1_schema_pragma.txt):
activity_log (+ garden_id per D1), overview_versions (no garden_id — it is a
polymorphic child of scoped entities), artifacts (+ garden_id; filename keeps
its v1 UNIQUE).

NEW in v2 (from the approved plan): pipeline_runs, resolver_feedback, ai_usage.

v1 index names are recreated verbatim for the carried tables. Columns not
explicitly NOT NULL keep v1 nullability (carried) or are nullable (new).
"""
from __future__ import annotations

from datetime import datetime

import sqlalchemy as sa
from sqlalchemy import (
    BigInteger,
    Double,
    ForeignKey,
    Index,
    Integer,
    Text,
)
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.orm import Mapped, mapped_column

from app.models.base import Base, created_at_column, garden_fk, pk_column


class ActivityLog(Base):
    """Carried v1 table: every user mutation with old->new diffs for edits."""

    __tablename__ = "activity_log"

    id: Mapped[int] = pk_column()
    garden_id: Mapped[int] = garden_fk()
    created_at: Mapped[datetime] = created_at_column()
    category: Mapped[str] = mapped_column(Text, nullable=False)
    entity_type: Mapped[str] = mapped_column(Text, nullable=False)
    entity_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
    summary: Mapped[str] = mapped_column(Text, nullable=False)
    field: Mapped[str | None] = mapped_column(Text, nullable=True)
    old_value: Mapped[str | None] = mapped_column(Text, nullable=True)
    new_value: Mapped[str | None] = mapped_column(Text, nullable=True)

    __table_args__ = (
        Index("idx_activity_log_category", "category", "created_at"),
        Index(
            "idx_activity_log_entity", "entity_type", "entity_id", "created_at"
        ),
        Index("idx_activity_log_created", "created_at"),
    )


class OverviewVersion(Base):
    """Carried v1 table: version history of entity overview/notes content.

    No garden_id — polymorphic child; scope resolves through the parent entity.
    """

    __tablename__ = "overview_versions"

    id: Mapped[int] = pk_column()
    entity_type: Mapped[str] = mapped_column(Text, nullable=False)
    entity_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
    content: Mapped[str] = mapped_column(Text, nullable=False)
    created_at: Mapped[datetime] = created_at_column()
    author: Mapped[str] = mapped_column(
        Text, nullable=False, server_default=sa.text("'user'")
    )
    change_note: Mapped[str | None] = mapped_column(
        Text, nullable=True, server_default=sa.text("''")
    )

    __table_args__ = (
        Index(
            "idx_overview_versions_entity",
            "entity_type",
            "entity_id",
            "created_at",
        ),
    )


class Artifact(Base):
    """Carried v1 table: AI-generated markdown artifacts (plans, schedules)."""

    __tablename__ = "artifacts"

    id: Mapped[int] = pk_column()
    garden_id: Mapped[int] = garden_fk()
    kind: Mapped[str] = mapped_column(Text, nullable=False)
    title: Mapped[str] = mapped_column(Text, nullable=False)
    filename: Mapped[str] = mapped_column(Text, nullable=False, unique=True)
    generated_at: Mapped[datetime] = mapped_column(
        sa.DateTime(timezone=True), nullable=False
    )
    notes: Mapped[str | None] = mapped_column(
        Text, nullable=True, server_default=sa.text("''")
    )

    __table_args__ = (Index("idx_artifacts_kind", "kind"),)


class PipelineRun(Base):
    """NEW: one stage of the capture-processing AI pipeline for a field note."""

    __tablename__ = "pipeline_runs"

    id: Mapped[int] = pk_column()
    capture_id: Mapped[int] = mapped_column(
        BigInteger, ForeignKey("field_notes.id"), nullable=False, index=True
    )
    stage: Mapped[str] = mapped_column(Text, nullable=False)
    model: Mapped[str | None] = mapped_column(
        Text, nullable=True, server_default=sa.text("''")
    )
    prompt_version: Mapped[str | None] = mapped_column(
        Text, nullable=True, server_default=sa.text("''")
    )
    output: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
    confidence: Mapped[float | None] = mapped_column(Double, nullable=True)
    latency_ms: Mapped[int | None] = mapped_column(Integer, nullable=True)
    tokens: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
    created_at: Mapped[datetime] = created_at_column()


class ResolverFeedback(Base):
    """NEW: user corrections to entity-mention resolution, for prompt tuning."""

    __tablename__ = "resolver_feedback"

    id: Mapped[int] = pk_column()
    garden_id: Mapped[int] = garden_fk()
    note_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("notes.id"), nullable=True
    )
    mention: Mapped[str] = mapped_column(Text, nullable=False)
    wrong_target: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
    correct_target: Mapped[dict] = mapped_column(JSONB, nullable=False)
    created_at: Mapped[datetime] = created_at_column()


class AiUsage(Base):
    """NEW: per-call AI token/cost accounting across all jobs."""

    __tablename__ = "ai_usage"

    id: Mapped[int] = pk_column()
    garden_id: Mapped[int] = garden_fk()
    job: Mapped[str] = mapped_column(Text, nullable=False)
    provider: Mapped[str] = mapped_column(Text, nullable=False)
    model: Mapped[str] = mapped_column(Text, nullable=False)
    input_tokens: Mapped[int | None] = mapped_column(
        Integer, nullable=True, server_default=sa.text("0")
    )
    output_tokens: Mapped[int | None] = mapped_column(
        Integer, nullable=True, server_default=sa.text("0")
    )
    cached_input_tokens: Mapped[int | None] = mapped_column(
        Integer, nullable=True, server_default=sa.text("0")
    )
    cost_usd: Mapped[float | None] = mapped_column(Double, nullable=True)
    capture_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
    session_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
    created_at: Mapped[datetime] = created_at_column()

    __table_args__ = (
        # Explicit name: the convention name would collide with the plain
        # garden_id index that garden_fk() already creates.
        Index("ix_ai_usage_garden_created", "garden_id", "created_at"),
    )
