"""Chat module models.

Implements the two tables of the approved Chat database design
(``docs/02_Architecture/Database/04_Chat.md``):

* :class:`Conversation` (``conversations``)
* :class:`Message` (``messages``)

A conversation is unique for one buyer, one seller and one listing. Each
participant has an **independent** chat quota tracked on the conversation:
``{buyer,seller}_messages_used`` (counted text messages sent),
``{buyer,seller}_unlocked_messages`` (extra capacity earned by watching rewarded
ads) and ``{buyer,seller}_ads_watched`` (unlock count, for the abuse guard). The
free allowance itself is a config constant (``FREE_MESSAGE_LIMIT``), so the
remaining allowance for a user is ``FREE_MESSAGE_LIMIT + unlocked - used``.
"""

from __future__ import annotations

import enum

from app.extensions import db
from app.models.base import BaseModel, BigIntegerType
from app.models.enums import OfferStatus
from app.models.mixins import CreatedAtMixin, PublicIdMixin, TimestampMixin


class ConversationStatus(enum.Enum):
    """Lifecycle status of a conversation."""

    ACTIVE = "active"
    CLOSED = "closed"


class MessageType(enum.Enum):
    """Type of a chat message."""

    TEXT = "text"
    SYSTEM = "system"


def _enum_values(enum_cls):
    """Persist the ENUM member *values* (e.g. ``"active"``) rather than names."""
    return [member.value for member in enum_cls]


class Conversation(BaseModel, PublicIdMixin, TimestampMixin):
    """A buyer/seller conversation about a single listing."""

    __tablename__ = "conversations"
    __table_args__ = (
        db.UniqueConstraint(
            "buyer_id", "seller_id", "listing_id",
            name="uq_conversations_buyer_seller_listing",
        ),
        db.Index("ix_conversations_created_at", "created_at"),
    )

    listing_id = db.Column(
        BigIntegerType, db.ForeignKey("listings.id"), nullable=False, index=True
    )
    buyer_id = db.Column(
        BigIntegerType, db.ForeignKey("users.id"), nullable=False, index=True
    )
    seller_id = db.Column(
        BigIntegerType, db.ForeignKey("users.id"), nullable=False, index=True
    )
    status = db.Column(
        db.Enum(ConversationStatus, values_callable=_enum_values, name="conversation_status"),
        nullable=False,
        default=ConversationStatus.ACTIVE,
        index=True,
    )
    conversation_started_at = db.Column(db.DateTime, nullable=False, default=db.func.now())
    first_reply_at = db.Column(db.DateTime, nullable=True)
    # Per-participant chat quota. Each side counts up independently; one user
    # sending messages never touches the other's counters.
    buyer_messages_used = db.Column(db.Integer, nullable=False, default=0, server_default="0")
    seller_messages_used = db.Column(db.Integer, nullable=False, default=0, server_default="0")
    buyer_unlocked_messages = db.Column(db.Integer, nullable=False, default=0, server_default="0")
    seller_unlocked_messages = db.Column(db.Integer, nullable=False, default=0, server_default="0")
    buyer_ads_watched = db.Column(db.Integer, nullable=False, default=0, server_default="0")
    seller_ads_watched = db.Column(db.Integer, nullable=False, default=0, server_default="0")
    is_premium_conversation = db.Column(
        db.Boolean, nullable=False, default=False, index=True
    )
    last_message_at = db.Column(db.DateTime, nullable=True, index=True)

    # Relationships. buyer/seller both reference users.id, so foreign_keys is
    # explicit to disambiguate.
    listing = db.relationship("Listing", backref="conversations")
    buyer = db.relationship("User", foreign_keys=[buyer_id])
    seller = db.relationship("User", foreign_keys=[seller_id])
    messages = db.relationship(
        "Message",
        back_populates="conversation",
        cascade="all, delete-orphan",
        order_by="Message.created_at",
    )
    offers = db.relationship(
        "Offer",
        back_populates="conversation",
        cascade="all, delete-orphan",
        order_by="Offer.created_at",
    )

    # --- Per-participant chat quota helpers --------------------------------
    # These centralise the buyer/seller branching so callers never duplicate it.

    def _participant_prefix(self, user) -> str | None:
        """Return ``"buyer"``/``"seller"`` for a participant, else ``None``."""
        if user.id == self.buyer_id:
            return "buyer"
        if user.id == self.seller_id:
            return "seller"
        return None

    def messages_used_by(self, user) -> int:
        """Counted text messages this user has sent in the conversation."""
        prefix = self._participant_prefix(user)
        return getattr(self, f"{prefix}_messages_used") if prefix else 0

    def unlocked_messages_for(self, user) -> int:
        """Extra messages this user has unlocked via rewarded ads."""
        prefix = self._participant_prefix(user)
        return getattr(self, f"{prefix}_unlocked_messages") if prefix else 0

    def ads_watched_by(self, user) -> int:
        """Number of rewarded unlocks this user has performed here."""
        prefix = self._participant_prefix(user)
        return getattr(self, f"{prefix}_ads_watched") if prefix else 0

    def record_message_used(self, user) -> None:
        """Increment the sender's counted-message tally by one."""
        prefix = self._participant_prefix(user)
        if prefix:
            setattr(self, f"{prefix}_messages_used", getattr(self, f"{prefix}_messages_used") + 1)

    def record_unlock(self, user, messages: int) -> None:
        """Add unlocked capacity for the user and record the ad view."""
        prefix = self._participant_prefix(user)
        if prefix:
            setattr(self, f"{prefix}_unlocked_messages", getattr(self, f"{prefix}_unlocked_messages") + messages)
            setattr(self, f"{prefix}_ads_watched", getattr(self, f"{prefix}_ads_watched") + 1)

    def __repr__(self) -> str:
        return f"<Conversation id={self.id} listing_id={self.listing_id}>"


class Message(BaseModel, PublicIdMixin, CreatedAtMixin):
    """A single message within a conversation. Immutable once created."""

    __tablename__ = "messages"
    __table_args__ = (db.Index("ix_messages_created_at", "created_at"),)

    conversation_id = db.Column(
        BigIntegerType, db.ForeignKey("conversations.id"), nullable=False, index=True
    )
    sender_id = db.Column(
        BigIntegerType, db.ForeignKey("users.id"), nullable=False, index=True
    )
    message = db.Column(db.Text, nullable=False)
    message_type = db.Column(
        db.Enum(MessageType, values_callable=_enum_values, name="message_type"),
        nullable=False,
        default=MessageType.TEXT,
        index=True,
    )
    read_at = db.Column(db.DateTime, nullable=True, index=True)
    deleted_at = db.Column(db.DateTime, nullable=True, index=True)

    conversation = db.relationship("Conversation", back_populates="messages")
    sender = db.relationship("User", foreign_keys=[sender_id])

    def __repr__(self) -> str:
        return f"<Message id={self.id} conversation_id={self.conversation_id}>"


class Offer(BaseModel, PublicIdMixin, TimestampMixin):
    """A buyer's pending or resolved offer in a conversation."""

    __tablename__ = "offers"
    __table_args__ = (
        db.Index("ix_offers_created_at", "created_at"),
        db.Index("ix_offers_status", "status"),
    )

    listing_id = db.Column(
        BigIntegerType, db.ForeignKey("listings.id"), nullable=False, index=True
    )
    conversation_id = db.Column(
        BigIntegerType, db.ForeignKey("conversations.id"), nullable=False, index=True
    )
    buyer_id = db.Column(
        BigIntegerType, db.ForeignKey("users.id"), nullable=False, index=True
    )
    seller_id = db.Column(
        BigIntegerType, db.ForeignKey("users.id"), nullable=False, index=True
    )
    amount = db.Column(db.Numeric(10, 2), nullable=False)
    optional_message = db.Column(db.String(300), nullable=True)
    status = db.Column(
        db.Enum(OfferStatus, values_callable=_enum_values, name="offer_status"),
        nullable=False,
        default=OfferStatus.PENDING,
    )

    listing = db.relationship("Listing", backref="offers")
    conversation = db.relationship("Conversation", back_populates="offers")
    buyer = db.relationship("User", foreign_keys=[buyer_id])
    seller = db.relationship("User", foreign_keys=[seller_id])

    def __repr__(self) -> str:
        return f"<Offer id={self.id} conversation_id={self.conversation_id}>"
