"""Reminders / notifications models (all NEW in v2 — from the approved plan).

Tables: scheduled_actions, care_rules, reminder_events, notification_channels,
notification_dispatches.

Vocabulary columns are TEXT + CheckConstraint (no PG native enums) per the
base.py conventions. Columns not explicitly marked NOT NULL in the plan are
nullable, mirroring the v1 idiom.
"""
from __future__ import annotations

from datetime import datetime

import sqlalchemy as sa
from sqlalchemy import (
    BigInteger,
    Boolean,
    CheckConstraint,
    ForeignKey,
    Index,
    Integer,
    Text,
    UniqueConstraint,
)
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 ScheduledAction(Base):
    """A single armed reminder/action the scheduler evaluates and fires."""

    __tablename__ = "scheduled_actions"

    id: Mapped[int] = pk_column()
    garden_id: Mapped[int] = garden_fk()
    assistant_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("assistants.id"), nullable=True
    )
    title: Mapped[str] = mapped_column(Text, nullable=False)
    body_md: Mapped[str | None] = mapped_column(
        Text, nullable=True, server_default=sa.text("''")
    )
    # Vocabulary: fertilize|prune|pest|water|plant|harvest|winterize|observe|custom
    action_type: Mapped[str] = mapped_column(Text, nullable=False)
    target_type: Mapped[str | None] = mapped_column(Text, nullable=True)
    target_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
    # Vocabulary: date|recurrence|weather|cadence|garden_state
    trigger_type: Mapped[str] = mapped_column(Text, nullable=False)
    trigger_config: Mapped[dict] = mapped_column(JSONB, nullable=False)
    # Vocabulary: digest|normal|urgent
    urgency: Mapped[str] = mapped_column(
        Text, nullable=False, server_default=sa.text("'normal'")
    )
    # Vocabulary: once|once_per_season|unlimited
    fire_limit: Mapped[str] = mapped_column(
        Text, nullable=False, server_default=sa.text("'once'")
    )
    next_eval_at: Mapped[datetime | None] = mapped_column(
        sa.DateTime(timezone=True), nullable=True
    )
    # Vocabulary: armed|fired|done|dismissed|expired|paused
    status: Mapped[str] = mapped_column(
        Text, nullable=False, server_default=sa.text("'armed'")
    )
    snooze_until: Mapped[datetime | None] = mapped_column(
        sa.DateTime(timezone=True), nullable=True
    )
    last_fired_at: Mapped[datetime | None] = mapped_column(
        sa.DateTime(timezone=True), nullable=True
    )
    completed_at: Mapped[datetime | None] = mapped_column(
        sa.DateTime(timezone=True), nullable=True
    )
    source_note_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("notes.id"), nullable=True
    )
    source_session_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("chat_sessions.id"), nullable=True
    )
    care_rule_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("care_rules.id"), nullable=True
    )
    # Vocabulary: user|assistant
    created_by: Mapped[str] = mapped_column(
        Text, nullable=False, server_default=sa.text("'user'")
    )
    created_at: Mapped[datetime] = created_at_column()
    updated_at: Mapped[datetime | None] = mapped_column(
        sa.DateTime(timezone=True), nullable=True
    )

    __table_args__ = (
        CheckConstraint(
            "action_type IN ('fertilize','prune','pest','water','plant',"
            "'harvest','winterize','observe','custom')",
            name="action_type",
        ),
        CheckConstraint(
            "trigger_type IN ('date','recurrence','weather','cadence','garden_state')",
            name="trigger_type",
        ),
        CheckConstraint(
            "urgency IN ('digest','normal','urgent')", name="urgency"
        ),
        CheckConstraint(
            "fire_limit IN ('once','once_per_season','unlimited')",
            name="fire_limit",
        ),
        CheckConstraint(
            "status IN ('armed','fired','done','dismissed','expired','paused')",
            name="status",
        ),
        CheckConstraint(
            "created_by IN ('user','assistant')", name="created_by"
        ),
        # Explicit name: the convention name would collide with the plain
        # garden_id index that garden_fk() already creates.
        Index(
            "ix_scheduled_actions_garden_status_eval",
            "garden_id",
            "status",
            "next_eval_at",
        ),
    )


class CareRule(Base):
    """Per-species/variety care cadence rule that seeds scheduled_actions."""

    __tablename__ = "care_rules"

    id: Mapped[int] = pk_column()
    garden_id: Mapped[int] = garden_fk()
    species_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("species.id"), nullable=True
    )
    variety_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("varieties.id"), nullable=True
    )
    action: Mapped[str] = mapped_column(Text, nullable=False)
    cadence: Mapped[dict] = mapped_column(JSONB, nullable=False)
    condition: Mapped[dict | None] = mapped_column(JSONB, nullable=True)
    note: Mapped[str | None] = mapped_column(
        Text, nullable=True, server_default=sa.text("''")
    )
    # Vocabulary: seed_default|assistant|user
    source: Mapped[str] = mapped_column(
        Text, nullable=False, server_default=sa.text("'seed_default'")
    )
    enabled: Mapped[bool | None] = mapped_column(
        Boolean, nullable=True, server_default=sa.text("true")
    )
    created_at: Mapped[datetime] = created_at_column()

    __table_args__ = (
        CheckConstraint(
            "source IN ('seed_default','assistant','user')", name="source"
        ),
    )


class ReminderEvent(Base):
    """One firing of a scheduled_action and the user's response to it."""

    __tablename__ = "reminder_events"

    id: Mapped[int] = pk_column()
    scheduled_action_id: Mapped[int] = mapped_column(
        BigInteger, ForeignKey("scheduled_actions.id"), nullable=False
    )
    fired_at: Mapped[datetime] = mapped_column(
        sa.DateTime(timezone=True), nullable=False
    )
    rendered_text: Mapped[str | None] = mapped_column(
        Text, nullable=True, server_default=sa.text("''")
    )
    channels: Mapped[dict | None] = mapped_column(
        JSONB, nullable=True, server_default=sa.text("'{}'::jsonb")
    )
    acknowledged_at: Mapped[datetime | None] = mapped_column(
        sa.DateTime(timezone=True), nullable=True
    )
    done_at: Mapped[datetime | None] = mapped_column(
        sa.DateTime(timezone=True), nullable=True
    )
    dismissed_at: Mapped[datetime | None] = mapped_column(
        sa.DateTime(timezone=True), nullable=True
    )
    done_note_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("notes.id"), nullable=True
    )


class NotificationChannel(Base):
    """A delivery endpoint (web push subscription, telegram chat) per user."""

    __tablename__ = "notification_channels"

    id: Mapped[int] = pk_column()
    garden_id: Mapped[int] = garden_fk()
    user_id: Mapped[int] = mapped_column(
        BigInteger, ForeignKey("users.id"), nullable=False
    )
    # Vocabulary: webpush|telegram
    kind: Mapped[str] = mapped_column(Text, nullable=False)
    address: Mapped[str] = mapped_column(Text, nullable=False)
    config: Mapped[dict | None] = mapped_column(
        JSONB, nullable=True, server_default=sa.text("'{}'::jsonb")
    )
    is_active: Mapped[bool | None] = mapped_column(
        Boolean, nullable=True, server_default=sa.text("true")
    )
    failure_count: Mapped[int | None] = mapped_column(
        Integer, nullable=True, server_default=sa.text("0")
    )
    created_at: Mapped[datetime] = created_at_column()
    last_used_at: Mapped[datetime | None] = mapped_column(
        sa.DateTime(timezone=True), nullable=True
    )

    __table_args__ = (
        CheckConstraint("kind IN ('webpush','telegram')", name="kind"),
        UniqueConstraint("kind", "address"),
    )


class NotificationDispatch(Base):
    """One attempted delivery of a notification to one channel (dedup by fire_key)."""

    __tablename__ = "notification_dispatches"

    id: Mapped[int] = pk_column()
    garden_id: Mapped[int] = garden_fk()
    scheduled_action_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("scheduled_actions.id"), nullable=True
    )
    channel_id: Mapped[int | None] = mapped_column(
        BigInteger, ForeignKey("notification_channels.id"), nullable=True
    )
    # Vocabulary: reminder|proactive|digest|system
    kind: Mapped[str] = mapped_column(Text, nullable=False)
    fire_key: Mapped[str] = mapped_column(Text, nullable=False)
    title: Mapped[str | None] = mapped_column(
        Text, nullable=True, server_default=sa.text("''")
    )
    body: Mapped[str | None] = mapped_column(
        Text, nullable=True, server_default=sa.text("''")
    )
    payload: Mapped[dict | None] = mapped_column(
        JSONB, nullable=True, server_default=sa.text("'{}'::jsonb")
    )
    # Vocabulary: pending|sent|failed|suppressed
    status: Mapped[str] = mapped_column(Text, nullable=False)
    error_code: Mapped[str | None] = mapped_column(Text, nullable=True)
    created_at: Mapped[datetime] = created_at_column()
    sent_at: Mapped[datetime | None] = mapped_column(
        sa.DateTime(timezone=True), nullable=True
    )

    __table_args__ = (
        CheckConstraint(
            "kind IN ('reminder','proactive','digest','system')", name="kind"
        ),
        CheckConstraint(
            "status IN ('pending','sent','failed','suppressed')", name="status"
        ),
        UniqueConstraint("fire_key", "channel_id"),
    )
