"""MpsNotification — in-app notifications surfaced as a badge near the
user's profile name and a list on /u/notifications.

One row per (recipient user × event). Created alongside the existing
transactional email sends — every email_offer_* / send_purchase /
send_sale call site also drops a notification so the user can find
it later in-app even if they never opened the email.

Schema is lighter than Remarkbox's (no separate Watcher table) because
recipients are implicit per kind: offer-received → shop owners,
offer-accepted → buyer, etc. The kind column carries the discriminator.
"""

import uuid

from sqlalchemy import Column, BigInteger, Boolean, Unicode, UnicodeText, Index
from sqlalchemy.orm import relationship

from .meta import (
    Base,
    RBase,
    UUIDType,
    now_timestamp,
    foreign_key,
    get_object_by_id,
)


# Notification kinds — keep these strings stable; they appear in
# rendered email subjects and the per-row icon picker in the UI.
NOTIFICATION_KIND_OFFER_RECEIVED = "offer_received"
NOTIFICATION_KIND_OFFER_ACCEPTED = "offer_accepted"
NOTIFICATION_KIND_OFFER_COUNTERED = "offer_countered"
NOTIFICATION_KIND_OFFER_DECLINED = "offer_declined"
NOTIFICATION_KIND_OFFER_WITHDRAWN = "offer_withdrawn"
NOTIFICATION_KIND_OFFER_BUYER_CANCELLED = "offer_buyer_cancelled"
NOTIFICATION_KIND_OFFER_EXPIRED = "offer_expired"
NOTIFICATION_KIND_PURCHASE = "purchase"
NOTIFICATION_KIND_SALE = "sale"
NOTIFICATION_KIND_AUCTION_OUTBID = "auction_outbid"
NOTIFICATION_KIND_AUCTION_WON = "auction_won"
NOTIFICATION_KIND_AUCTION_ENDED_NO_WINNER = "auction_ended_no_winner"
NOTIFICATION_KIND_AUCTION_CANCELLED = "auction_cancelled"


class MpsNotification(RBase, Base):
    id = Column(UUIDType, primary_key=True, index=True)

    # Recipient.
    user_id = Column(
        UUIDType, foreign_key("User", "id"), nullable=False, index=True,
    )

    # The shop this notification is about (nullable for cross-shop
    # cases). Useful for filtering "notifications for shop X" or
    # rendering the shop name in the row.
    shop_id = Column(UUIDType, foreign_key("Shop", "id"), nullable=True)

    # Discriminator — one of the NOTIFICATION_KIND_* constants.
    kind = Column(Unicode(64), nullable=False)

    # Short headline and one-line body shown in the list. Kept in the
    # row (not regenerated from FKs) so deleting the underlying offer
    # / auction / invoice doesn't blank the notification history.
    subject = Column(Unicode(256), nullable=False)
    body = Column(UnicodeText, nullable=True)

    # Click-through target. Relative path (e.g. "/o/<id>").
    link_url = Column(Unicode(512), nullable=True)

    # Optional FK back-references for breadcrumb rendering. All nullable —
    # not every notification kind has all four. Set whichever applies
    # at create time. Templates walk these to surface "shop → product →
    # offer/auction/invoice" trail back to the source.
    offer_id = Column(UUIDType, foreign_key("MpsOffer", "id"), nullable=True)
    auction_id = Column(
        UUIDType, foreign_key("MpsAuction", "id"), nullable=True,
    )
    invoice_id = Column(UUIDType, foreign_key("Invoice", "id"), nullable=True)

    created_timestamp = Column(BigInteger, nullable=False)
    updated_timestamp = Column(BigInteger, nullable=False)

    # Read state. read=False rows count toward the unread badge.
    read = Column(Boolean, nullable=False, default=False, server_default="0")
    read_timestamp = Column(BigInteger, nullable=True)

    user = relationship(argument="User", uselist=False, lazy="joined")
    shop = relationship(argument="Shop", uselist=False, lazy="joined")
    offer = relationship(argument="MpsOffer", uselist=False, lazy="joined")
    auction = relationship(argument="MpsAuction", uselist=False, lazy="joined")
    invoice = relationship(argument="Invoice", uselist=False, lazy="joined")

    __table_args__ = (
        Index(
            "ix_mps_notification_user_read_created",
            "user_id", "read", "created_timestamp",
        ),
    )

    def __init__(
        self,
        user,
        kind,
        subject,
        body=None,
        link_url=None,
        shop=None,
        offer=None,
        auction=None,
        invoice=None,
    ):
        self.id = uuid.uuid1()
        self.user = user
        self.kind = kind
        self.subject = subject
        self.body = body
        self.link_url = link_url
        self.shop = shop
        self.offer = offer
        self.auction = auction
        self.invoice = invoice
        ts = now_timestamp()
        self.created_timestamp = ts
        self.updated_timestamp = ts
        self.read = False

    @property
    def breadcrumbs(self):
        """Render an ordered list of (label, url) tuples that walk back
        from this notification to its source entities. Templates iterate
        this for the "Shop → Product → Offer" trail under each row.
        Each step is optional — only the entities that are still alive
        and have a usable URL show up.
        """
        crumbs = []
        if self.shop is not None:
            crumbs.append((self.shop.name, f"/s/{self.shop.uuid_str}"))

        # Product comes from offer/auction directly, or from the first
        # invoice line item for purchase/sale notifications.
        product = None
        if self.offer is not None and self.offer.product is not None:
            product = self.offer.product
        elif self.auction is not None and self.auction.product is not None:
            product = self.auction.product
        elif self.invoice is not None:
            try:
                first_item = self.invoice.line_items.first()
                if first_item is not None and first_item.product is not None:
                    product = first_item.product
            except Exception:
                pass
        if product is not None:
            crumbs.append((product.title, f"/p/{product.uuid_str}"))

        if self.offer is not None:
            crumbs.append(("Offer", f"/o/{self.offer.uuid_str}"))
        if self.auction is not None:
            crumbs.append(("Auction", f"/a/{self.auction.uuid_str}"))
        if self.invoice is not None:
            crumbs.append(("Invoice", f"/i/{self.invoice.uuid_str}"))
        return crumbs

    def mark_read(self, now_ms=None):
        """Idempotent — already-read rows are left alone."""
        if self.read:
            return
        ts = now_ms or now_timestamp()
        self.read = True
        self.read_timestamp = ts
        self.updated_timestamp = ts


def get_notification_by_id(dbsession, notification_id):
    return get_object_by_id(dbsession, notification_id, MpsNotification)


def count_unread_notifications(dbsession, user):
    """Cheap unread-count for the profile badge — no rows fetched."""
    if user is None:
        return 0
    return (
        dbsession.query(MpsNotification)
        .filter(
            MpsNotification.user_id == user.id,
            MpsNotification.read == False,  # noqa: E712 — SQLAlchemy idiom
        )
        .count()
    )


def get_notifications_for_user(
    dbsession, user, limit=50, offset=0, unread_only=False,
):
    """Return the user's notifications, newest first."""
    q = (
        dbsession.query(MpsNotification)
        .filter(MpsNotification.user_id == user.id)
        .order_by(MpsNotification.created_timestamp.desc())
    )
    if unread_only:
        q = q.filter(MpsNotification.read == False)  # noqa: E712
    return q.limit(limit).offset(offset).all()
