"""Database models for FenLiu."""

from __future__ import annotations

from datetime import datetime
from typing import Any

from sqlalchemy import JSON
from sqlalchemy import Boolean
from sqlalchemy import DateTime
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import Text
from sqlalchemy import UniqueConstraint
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func

from fenliu.database import Base

# Constants for repr truncation
ERROR_REASON_REPR_LENGTH = 50


class HashtagStream(Base):
    """Model for monitored hashtag streams."""

    __tablename__ = "hashtag_streams"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    hashtag: Mapped[str] = mapped_column(String(100), nullable=False, index=True, unique=True)
    instance: Mapped[str] = mapped_column(String(200), nullable=False, default="mastodon.social")
    active: Mapped[bool] = mapped_column(Boolean, default=True)
    last_check: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    updated_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), onupdate=func.now(), nullable=True)

    # Scheduling fields
    enable_scheduling: Mapped[bool] = mapped_column(Boolean, default=True)
    fetch_interval_minutes: Mapped[int] = mapped_column(Integer, default=60)
    next_scheduled_fetch: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    last_fetch_count: Mapped[int | None] = mapped_column(Integer, nullable=True)

    posts: Mapped[list[Post]] = relationship("Post", back_populates="stream", cascade="all, delete-orphan")

    def __repr__(self) -> str:
        """Return string representation of HashtagStream."""
        return f"HashtagStream(id={self.id}, hashtag='{self.hashtag}', instance='{self.instance}')"


class Post(Base):
    """Model for Fediverse posts."""

    __tablename__ = "posts"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    post_id: Mapped[str] = mapped_column(String(100), nullable=False, index=True, unique=True)
    content: Mapped[str] = mapped_column(Text, nullable=False)
    author_username: Mapped[str | None] = mapped_column(String(100), nullable=True)
    author_display_name: Mapped[str | None] = mapped_column(String(200), nullable=True)
    author_url: Mapped[str | None] = mapped_column(String(500), nullable=True)
    author_is_bot: Mapped[bool] = mapped_column(Boolean, default=False)
    instance: Mapped[str] = mapped_column(String(200), nullable=False)
    hashtags: Mapped[dict[str, Any] | list[str] | None] = mapped_column(JSON, nullable=True)
    media_attachments: Mapped[dict[str, Any] | list[dict[str, Any]] | None] = mapped_column(JSON, nullable=True)
    boosts: Mapped[int] = mapped_column(Integer, default=0)
    likes: Mapped[int] = mapped_column(Integer, default=0)
    replies: Mapped[int] = mapped_column(Integer, default=0)
    url: Mapped[str | None] = mapped_column(String(500), nullable=True)
    created_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    fetched_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
    processed: Mapped[bool] = mapped_column(Boolean, default=False)
    spam_score: Mapped[int] = mapped_column(Integer, default=0)  # 0-100 scale

    # Review and learning system fields
    reviewed: Mapped[bool] = mapped_column(Boolean, default=False)
    reviewed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    reviewer_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
    manual_spam_score: Mapped[int | None] = mapped_column(Integer, nullable=True)  # 0-100, manually adjusted score
    approved: Mapped[bool | None] = mapped_column(
        Boolean, nullable=True
    )  # True = approved, False = rejected, None = not reviewed
    curated_exported: Mapped[bool] = mapped_column(Boolean, default=False)  # Whether exported to curated queue
    export_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    training_data: Mapped[bool] = mapped_column(
        Boolean, default=False
    )  # Whether used as training data for learning system

    # Curated queue state machine: pending → reserved → delivered | error
    queue_status: Mapped[str | None] = mapped_column(String(20), nullable=True, index=True)
    reserved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
    delivered_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True)
    error_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
    errored_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)

    stream_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("hashtag_streams.id"), nullable=True)
    stream: Mapped[HashtagStream | None] = relationship("HashtagStream", back_populates="posts")

    def __repr__(self) -> str:
        """Return string representation of Post."""
        return f"Post(id={self.id}, post_id='{self.post_id}', author='{self.author_username}')"


class ReviewFeedback(Base):
    """Model for storing historical review decisions and feedback."""

    __tablename__ = "review_feedback"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    post_id: Mapped[int] = mapped_column(Integer, ForeignKey("posts.id"), nullable=False, index=True)
    decision: Mapped[str] = mapped_column(String(20), nullable=False)  # "approved", "rejected", "score_adjusted"
    manual_score: Mapped[int | None] = mapped_column(Integer, nullable=True)  # Manually set score if applicable
    reviewer_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    # Relationships
    post: Mapped[Post] = relationship("Post", backref="review_feedback")

    def __repr__(self) -> str:
        """Return string representation of ReviewFeedback."""
        return f"ReviewFeedback(id={self.id}, post_id={self.post_id}, decision='{self.decision}')"


class BlockedUser(Base):
    """Model for the 'Don't Reblog' user blocklist.

    Supports both exact matching and pattern matching:
    - exact: exact account identifier match (default)
    - suffix: ends with pattern
    - prefix: starts with pattern
    - contains: substring match
    """

    __tablename__ = "blocked_users"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    account_identifier: Mapped[str] = mapped_column(
        String(500), nullable=False, index=True
    )  # e.g. "@user@instance.social" or pattern like "*.bsky.app"
    pattern_type: Mapped[str] = mapped_column(
        String(20), nullable=False, default="exact"
    )  # "exact", "suffix", "prefix", "contains"
    notes: Mapped[str | None] = mapped_column(Text, nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    def __repr__(self) -> str:
        """Return string representation of BlockedUser."""
        return f"BlockedUser(id={self.id}, account='{self.account_identifier}', type='{self.pattern_type}')"


class BlockedHashtag(Base):
    """Model for the 'Don't Reblog' hashtag blocklist."""

    __tablename__ = "blocked_hashtags"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    hashtag: Mapped[str] = mapped_column(
        String(100), nullable=False, unique=True, index=True
    )  # stored lowercase, without leading #
    notes: Mapped[str | None] = mapped_column(Text, nullable=True)
    created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    __table_args__ = (UniqueConstraint("hashtag", name="uq_blocked_hashtag"),)

    def __repr__(self) -> str:
        """Return string representation of BlockedHashtag."""
        return f"BlockedHashtag(id={self.id}, hashtag='#{self.hashtag}')"


class AppSetting(Base):
    """Model for application-wide settings stored as key/value pairs."""

    __tablename__ = "app_settings"

    key: Mapped[str] = mapped_column(String(100), primary_key=True)
    value: Mapped[str] = mapped_column(Text, nullable=False)  # JSON-encoded
    updated_at: Mapped[datetime] = mapped_column(
        DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
    )

    def __repr__(self) -> str:
        """Return string representation of AppSetting."""
        return f"AppSetting(key='{self.key}', value='{self.value}')"


class QueueStats(Base):
    """Model for tracking aggregate queue statistics that survive post deletion."""

    __tablename__ = "queue_stats"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    total_deleted_delivered: Mapped[int] = mapped_column(Integer, default=0)
    total_deleted_reserved: Mapped[int] = mapped_column(Integer, default=0)
    total_deleted_pending: Mapped[int] = mapped_column(Integer, default=0)

    def __repr__(self) -> str:
        """Return string representation of QueueStats."""
        return (
            f"QueueStats(delivered={self.total_deleted_delivered}, "
            f"reserved={self.total_deleted_reserved}, pending={self.total_deleted_pending})"
        )


class ErrorHistory(Base):
    """Historical record of deleted error posts with their reasons."""

    __tablename__ = "error_history"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
    error_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
    deleted_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())

    def __repr__(self) -> str:
        """Return string representation of ErrorHistory."""
        reason = (
            self.error_reason[:ERROR_REASON_REPR_LENGTH] + "..."
            if self.error_reason and len(self.error_reason) > ERROR_REASON_REPR_LENGTH
            else self.error_reason
        )
        return f"ErrorHistory(id={self.id}, reason='{reason}', deleted_at={self.deleted_at})"
