"""Thin orchestration layer over MpsNotification — one call per
event, dropping the right row(s) for the right recipient(s).

Every transactional email surface in MPS (purchase, sale, auction
outbid, offer state transitions) should pair with a notification
persist so the user has a permanent in-app inbox independent of
whether their email client rendered the message. This module is the
shared call site for the non-offer surfaces; offer transitions live
inline in views/offer.py via _notify_offer_event for symmetry with
the per-action state guards there.

All helpers are non-fatal — a notification persist failure logs and
moves on. The HTTP response must never 500 because the inbox row
couldn't be written.
"""

import logging

from ..models.notification import (
    MpsNotification,
    NOTIFICATION_KIND_PURCHASE,
    NOTIFICATION_KIND_SALE,
    NOTIFICATION_KIND_AUCTION_OUTBID,
    NOTIFICATION_KIND_AUCTION_WON,
    NOTIFICATION_KIND_AUCTION_ENDED_NO_WINNER,
    NOTIFICATION_KIND_AUCTION_CANCELLED,
    NOTIFICATION_KIND_OFFER_EXPIRED,
)


log = logging.getLogger(__name__)


def _resolve_session(target):
    """Accept either a Pyramid request or a SQLAlchemy Session.
    Tick jobs pass the session directly; views pass `request`. This
    isolation lets the same orchestrator work in both contexts.
    """
    dbsession = getattr(target, "dbsession", None)
    return dbsession if dbsession is not None else target


def _safe_add(target, notification):
    try:
        _resolve_session(target).add(notification)
    except Exception:
        log.exception("notification persist failed (non-fatal)")


def _invoice_first_product_title(invoice):
    """Best-effort label for purchase/sale notification subjects."""
    try:
        first = invoice.line_items.first()
        if first is not None and first.product is not None:
            return first.product.title
    except Exception:
        pass
    return None


def notify_purchase_and_sale(target, invoice):
    """Drop a purchase notification for the buyer and a sale
    notification for each shop owner on this invoice. Idempotent
    per (recipient × invoice × kind) is NOT enforced — callers wire
    this in once per cart-completion path, same as the email sends.
    Accepts a request or a dbsession.

    Non-fatal: any exception (incl. unit-test mocks with non-numeric
    totals) is logged and swallowed. A notification persist failure
    must not propagate up the payment-finalization stack.
    """
    if invoice is None:
        return
    try:
        _notify_purchase_and_sale_inner(target, invoice)
    except Exception:
        log.exception("notify_purchase_and_sale failed (non-fatal)")


def _notify_purchase_and_sale_inner(target, invoice):
    title_hint = _invoice_first_product_title(invoice)
    total_str = f"${invoice.total:.2f}"

    # Buyer side.
    if invoice.user is not None:
        purchase_subject = (
            f'Purchase complete: {title_hint}' if title_hint
            else "Purchase complete"
        )
        _safe_add(
            target,
            MpsNotification(
                user=invoice.user,
                kind=NOTIFICATION_KIND_PURCHASE,
                subject=purchase_subject,
                body=f'{total_str} charged. Click to view receipt.',
                link_url=f"/i/{invoice.uuid_str}",
                shop=invoice.shop,
                invoice=invoice,
            ),
        )

    # Seller side — every shop owner.
    if invoice.shop is not None:
        sale_subject = (
            f'New sale: {title_hint}' if title_hint else "New sale"
        )
        for owner in invoice.shop.owners:
            if owner is None:
                continue
            _safe_add(
                target,
                MpsNotification(
                    user=owner,
                    kind=NOTIFICATION_KIND_SALE,
                    subject=sale_subject,
                    body=f'{total_str} from {invoice.user.display_name}.'
                    if invoice.user else f'{total_str}.',
                    link_url=f"/i/{invoice.uuid_str}",
                    shop=invoice.shop,
                    invoice=invoice,
                ),
            )


def notify_auction_outbid(target, prior_bidder, auction):
    """Drop a notification for the previous winning bidder when a new
    higher bid lands. Mirrors the existing send_auction_outbid_email.
    Non-fatal — failures (incl. mock-typed inputs from unit tests) are
    logged and swallowed."""
    if prior_bidder is None or auction is None:
        return
    try:
        title_hint = auction.product.title if auction.product else "auction"
        _safe_add(
            target,
            MpsNotification(
                user=prior_bidder,
                kind=NOTIFICATION_KIND_AUCTION_OUTBID,
                subject=f'You were outbid on "{title_hint}"',
                body=f'Current high: ${auction.current_high:.2f}. Click to bid again.',
                link_url=f"/a/{auction.uuid_str}",
                shop=auction.shop,
                auction=auction,
            ),
        )
    except Exception:
        log.exception("notify_auction_outbid failed (non-fatal)")


def notify_auction_won(target, auction):
    """Drop a notification for the auction winner when the auction
    transitions ACTIVE → ENDED with a winner. Fired by auction_tick;
    the buyer needs the pay-by deadline before it lapses. Non-fatal."""
    if auction is None or auction.winner is None:
        return
    try:
        title_hint = auction.product.title if auction.product else "auction"
        _safe_add(
            target,
            MpsNotification(
                user=auction.winner,
                kind=NOTIFICATION_KIND_AUCTION_WON,
                subject=f'You won the auction for "{title_hint}"',
                body=(
                    f'Pay ${auction.current_high:.2f} to claim it. '
                    "Auction will be released to the next-highest bidder if "
                    "payment lapses."
                ),
                link_url=f"/a/{auction.uuid_str}",
                shop=auction.shop,
                auction=auction,
            ),
        )
    except Exception:
        log.exception("notify_auction_won failed (non-fatal)")


def notify_auction_ended_no_winner(target, auction):
    """Drop a notification for shop owners when an auction ends with
    no winner (no bids or reserve not met). Lets the seller decide
    whether to relist. Non-fatal."""
    if auction is None or auction.shop is None:
        return
    try:
        _notify_auction_ended_no_winner_inner(target, auction)
    except Exception:
        log.exception("notify_auction_ended_no_winner failed (non-fatal)")


def _notify_auction_ended_no_winner_inner(target, auction):
    title_hint = auction.product.title if auction.product else "auction"
    for owner in auction.shop.owners:
        if owner is None:
            continue
        _safe_add(
            target,
            MpsNotification(
                user=owner,
                kind=NOTIFICATION_KIND_AUCTION_ENDED_NO_WINNER,
                subject=f'Auction ended without a winner: "{title_hint}"',
                body=(
                    "No bids met the reserve. Consider relisting at a lower "
                    "start price or with no reserve."
                ),
                link_url=f"/a/{auction.uuid_str}",
                shop=auction.shop,
                auction=auction,
            ),
        )


def notify_offer_expired(target, offer):
    """Drop notifications when an offer auto-expires — both pre-accept
    (seller didn't respond in the negotiation window) and post-accept
    (buyer never paid). Both buyer and seller(s) get a row. Non-fatal."""
    if offer is None:
        return
    try:
        _notify_offer_expired_inner(target, offer)
    except Exception:
        log.exception("notify_offer_expired failed (non-fatal)")


def _notify_offer_expired_inner(target, offer):
    title_hint = offer.product.title if offer.product else "offer"
    subject = f'Offer expired: "{title_hint}"'
    body = f'${offer.current_amount:.2f} offer auto-expired.'

    # Buyer.
    if offer.buyer is not None:
        _safe_add(
            target,
            MpsNotification(
                user=offer.buyer,
                kind=NOTIFICATION_KIND_OFFER_EXPIRED,
                subject=subject,
                body=body,
                link_url=f"/o/{offer.uuid_str}",
                shop=offer.shop,
                offer=offer,
            ),
        )
    # Seller(s).
    if offer.shop is not None:
        for owner in offer.shop.owners:
            if owner is None:
                continue
            _safe_add(
                target,
                MpsNotification(
                    user=owner,
                    kind=NOTIFICATION_KIND_OFFER_EXPIRED,
                    subject=subject,
                    body=body,
                    link_url=f"/o/{offer.uuid_str}",
                    shop=offer.shop,
                    offer=offer,
                ),
            )
