"""Marketplace routes: browse, create, view and serve listing images.

Routes stay thin — they handle requests, populate form choices, call the service
layer and render templates. All URLs are built with ``url_for``; uploaded images
are served exclusively through :func:`listing_image`.
"""

from __future__ import annotations

from flask import (
    current_app,
    flash,
    redirect,
    render_template,
    request,
    send_from_directory,
    url_for,
)
from flask_login import current_user, login_required

from app.extensions import db
from app.marketplace import marketplace_bp
from app.marketplace.forms import EditListingForm, ListingForm
from app.marketplace.services import (
    FeatureListingError,
    SelfFavoriteError,
    TransactionError,
    add_favorite,
    complete_transaction as complete_transaction_service,
    create_listing,
    expire_featured_listing,
    feature_listing,
    get_owned_listing_or_404,
    get_related_listings,
    get_offers_by_listing,
    get_seller_overview,
    get_user_favorites,
    get_user_listing_status_counts,
    get_user_listings,
    get_visible_listing_or_404,
    is_favorited,
    is_listing_currently_featured,
    record_listing_view,
    remove_favorite,
    soft_delete_listing,
    update_listing,
    user_has_listings,
)
from app.models import Category
from app.marketplace.discovery import get_similar_listings
from app.moderation.forms import ReportForm
from app.moderation.services import (
    CannotReportOwnListingError,
    DuplicateReportError,
    ReportError,
    create_report,
    get_open_report,
    get_reporter_reports,
)
from app.marketplace.analytics import get_dashboard_analytics
from app.premium.permissions import can_feature_listing
from app.premium.services import get_premium_seller_ids, is_user_currently_premium
from app.reviews.badges import get_badge_map, get_badges
from app.reviews.reputation import get_reputation, get_reputation_map
from app.reviews.services import get_review_context
from app.models.enums import ListingStatus

# Sort options for the My Listings toolbar: (query key, display label).
_SORT_OPTIONS = [
    ("newest", "Newest"),
    ("oldest", "Oldest"),
    ("price_low", "Price: Low to High"),
    ("price_high", "Price: High to Low"),
]


def _safe_next(default: str) -> str:
    """Return a safe, in-app redirect target from the POSTed ``next`` field."""
    target = request.form.get("next", "")
    if target.startswith("/") and not target.startswith("//"):
        return target
    return default


def _active_category_choices() -> list[tuple[int, str]]:
    """(id, name) choices for active categories, ordered for display."""
    categories = db.session.scalars(
        db.select(Category)
        .where(Category.is_active.is_(True))
        .order_by(Category.display_order, Category.name)
    )
    return [(c.id, c.name) for c in categories]


@marketplace_bp.route("/listings/new", methods=["GET", "POST"])
@login_required
def create_listing_view():
    form = ListingForm()
    form.category_id.choices = _active_category_choices()
    if not form.category_id.choices:
        flash("No categories are available yet. Please try again later.", "warning")

    if form.validate_on_submit():
        listing = create_listing(
            current_user,
            title=form.title.data,
            description=form.description.data,
            category_id=form.category_id.data,
            price=form.price.data,
            condition=form.condition.data,
            negotiable=form.negotiable.data,
            location=None,  # Meet-up location not collected in the MVP UI.
            images=form.images.data,
        )
        flash("Your listing has been published.", "success")
        return redirect(
            url_for("marketplace.listing_detail", public_id=listing.public_id)
        )

    return render_template("marketplace/form.html", form=form)


@marketplace_bp.route("/my-listings")
@login_required
def my_listings():
    page = request.args.get("page", 1, type=int)
    q = (request.args.get("q") or "").strip()

    sort = request.args.get("sort", "newest")
    if sort not in dict(_SORT_OPTIONS):
        sort = "newest"

    status = (request.args.get("status") or "").strip().lower()
    valid_statuses = {s.value for s in ListingStatus}
    status = status if status in valid_statuses else ""  # "" == all statuses

    pagination = get_user_listings(
        current_user, page, q=q or None, status=status or None, sort=sort
    )
    status_counts = get_user_listing_status_counts(current_user)
    # Premium Seller Analytics — computed only for Premium members (service layer);
    # free members get the upgrade card instead.
    is_premium = is_user_currently_premium(current_user)
    analytics = get_dashboard_analytics(current_user) if is_premium else None
    from app.activity.services import get_recent_activity
    return render_template(
        "marketplace/my_listings.html",
        pagination=pagination,
        premium_seller_ids=get_premium_seller_ids(pagination.items),
        offers_by_listing=get_offers_by_listing([l.id for l in pagination.items]),
        overview=get_seller_overview(current_user),
        is_premium=is_premium,
        analytics=analytics,
        recent_activity=get_recent_activity(current_user, limit=5),
        q=q,
        status=status,
        sort=sort,
        sort_options=_SORT_OPTIONS,
        statuses=[s.value for s in ListingStatus],
        has_any=user_has_listings(current_user),
        status_counts=status_counts,
    )


@marketplace_bp.route("/saved")
@login_required
def saved():
    page = request.args.get("page", 1, type=int)
    pagination = get_user_favorites(current_user, page)
    saved_listings = [fav.listing for fav in pagination.items]
    premium_seller_ids = get_premium_seller_ids(saved_listings)
    reputation_map = get_reputation_map(saved_listings)
    return render_template(
        "marketplace/saved.html",
        pagination=pagination,
        premium_seller_ids=premium_seller_ids,
        reputation_map=reputation_map,
        badge_map=get_badge_map(saved_listings, reputation_map=reputation_map),
    )


@marketplace_bp.route("/my-reports")
@login_required
def my_reports():
    """A reporter's own submitted reports and their current status (read-only).
    The destination for the reporter's 'Report Reviewed' notification."""
    return render_template("marketplace/my_reports.html", reports=get_reporter_reports(current_user))


@marketplace_bp.route("/listings/<public_id>/favorite", methods=["POST"])
@login_required
def favorite(public_id: str):
    listing = get_visible_listing_or_404(public_id)
    try:
        add_favorite(current_user, listing)
        flash("Saved to your items.", "success")
    except SelfFavoriteError:
        flash("You cannot save your own listing.", "warning")
    default = url_for("marketplace.listing_detail", public_id=public_id)
    return redirect(_safe_next(default))


@marketplace_bp.route("/listings/<public_id>/unfavorite", methods=["POST"])
@login_required
def unfavorite(public_id: str):
    listing = get_visible_listing_or_404(public_id)
    if remove_favorite(current_user, listing):
        flash("Removed from your saved items.", "success")
    return redirect(_safe_next(url_for("marketplace.saved")))


@marketplace_bp.route("/listings/<public_id>/edit", methods=["GET", "POST"])
@login_required
def edit_listing(public_id: str):
    listing = get_owned_listing_or_404(public_id, current_user)
    form = EditListingForm(obj=listing)
    form.category_id.choices = _active_category_choices()

    if request.method == "GET":
        # SelectFields need their stored value, not the enum / default.
        form.condition.data = listing.condition.value
        form.category_id.data = listing.category_id

    if form.validate_on_submit():
        update_listing(
            listing,
            current_user,
            title=form.title.data,
            description=form.description.data,
            category_id=form.category_id.data,
            price=form.price.data,
            condition=form.condition.data,
            negotiable=form.negotiable.data,
        )
        flash("Your listing has been updated.", "success")
        return redirect(
            url_for("marketplace.listing_detail", public_id=listing.public_id)
        )

    return render_template("marketplace/edit.html", form=form, listing=listing)


@marketplace_bp.route("/listings/<public_id>/delete", methods=["POST"])
@login_required
def delete_listing(public_id: str):
    listing = get_owned_listing_or_404(public_id, current_user)
    soft_delete_listing(listing, current_user)
    flash("Your listing has been deleted.", "success")
    return redirect(url_for("marketplace.my_listings"))


@marketplace_bp.route("/listings/<public_id>/feature", methods=["POST"])
@login_required
def feature_listing_view(public_id: str):
    """Feature one of the current user's listings (Premium only).

    Ownership + premium + available-status are all re-checked server-side, so a
    free user (or a direct API call) cannot feature a listing by hiding the UI.
    """
    listing = get_owned_listing_or_404(public_id, current_user)
    if not can_feature_listing(current_user):
        flash("Featured listings are a Premium feature. Upgrade to feature your listings.", "info")
        return redirect(url_for("premium.index"))
    if is_listing_currently_featured(listing):
        flash("This listing is already featured.", "info")
        return redirect(url_for("marketplace.my_listings"))
    try:
        replaced = feature_listing(listing, current_user)
    except FeatureListingError as exc:
        flash(str(exc), "warning")
    else:
        if replaced is not None:
            flash("⭐ Previous featured listing replaced successfully.", "success")
        else:
            flash("⭐ Your listing is now featured.", "success")
    return redirect(url_for("marketplace.my_listings"))


@marketplace_bp.route("/listings/<public_id>/complete", methods=["POST"])
@login_required
def complete_transaction(public_id: str):
    """Finalise the sale: create the CompletedTransaction (SOLD + COMPLETED).
    All business logic lives in the service; the route only handles the request."""
    listing = get_owned_listing_or_404(public_id, current_user)
    try:
        complete_transaction_service(listing, current_user)
    except TransactionError as exc:
        flash(str(exc), "warning")
    else:
        flash("Transaction completed. The sale has been finalised.", "success")
    return redirect(url_for("marketplace.listing_detail", public_id=public_id))


@marketplace_bp.route("/listings/<public_id>")
def listing_detail(public_id: str):
    listing = get_visible_listing_or_404(public_id)
    # Transparently retire a lapsed featured listing on access (no scheduled job).
    if listing.is_featured and not is_listing_currently_featured(listing):
        expire_featured_listing(listing)
    # Count a genuine, de-duplicated view (owner/refresh-spam handled in service).
    record_listing_view(listing, current_user, request)
    is_owner = current_user.is_authenticated and listing.seller_id == current_user.id
    is_saved = (
        current_user.is_authenticated
        and not is_owner
        and is_favorited(current_user, listing)
    )
    related = get_similar_listings(listing, limit=6)
    can_report = current_user.is_authenticated and not is_owner
    already_reported = bool(can_report and get_open_report(current_user, listing))
    # Review CTA for a completed transaction (buyer or seller); None otherwise.
    review_ctx = get_review_context(listing, current_user)
    return render_template(
        "marketplace/detail.html",
        listing=listing,
        is_owner=is_owner,
        is_saved=is_saved,
        related=related,
        premium_seller_ids=get_premium_seller_ids(related),
        reputation_map=get_reputation_map(related),
        badge_map=get_badge_map(related),
        seller_reputation=get_reputation(listing.seller_id),
        seller_badges=get_badges(listing.seller),
        can_report=can_report,
        already_reported=already_reported,
        report_form=ReportForm() if can_report else None,
        review_ctx=review_ctx,
    )


@marketplace_bp.route("/listings/<public_id>/report", methods=["POST"])
@login_required
def report_listing(public_id: str):
    """Submit a report against a listing (students). Business rules enforced in
    the service: no self-reports and no duplicate open reports."""
    listing = get_visible_listing_or_404(public_id)
    form = ReportForm()
    if not form.validate_on_submit():
        flash("Please choose a reason for your report.", "warning")
        return redirect(url_for("marketplace.listing_detail", public_id=public_id))
    try:
        create_report(current_user, listing, form.reason.data, form.description.data)
    except CannotReportOwnListingError:
        flash("You can't report your own listing.", "warning")
    except DuplicateReportError:
        flash("You already have an open report for this listing.", "info")
    except ReportError as exc:
        flash(str(exc), "warning")
    else:
        flash("Thanks — your report has been submitted to our moderators.", "success")
    return redirect(url_for("marketplace.listing_detail", public_id=public_id))


@marketplace_bp.route("/listings/image/<path:filename>")
def listing_image(filename: str):
    """Serve an uploaded listing image from outside the static directory."""
    return send_from_directory(current_app.config["LISTING_IMAGE_FOLDER"], filename)
