from pyramid.view import view_config

from pyramid.httpexceptions import HTTPFound, HTTPNotFound

from . import user_required, shop_is_ready_required

from ..models.user import (
    is_user_name_available,
    is_user_name_valid,
    get_user_by_name,
)

from ..models.invoice import Invoice
from ..models.offer import MpsOffer
from ..models.auction import MpsAuction, MpsBid
from ..models.shop import get_shop_by_id


@view_config(route_name="user_shops", renderer="shops.j2")
@user_required()
def user_shops(request):
    return {
        "the_title": "My Shops",
    }


@view_config(route_name="user_purchases", renderer="user_purchases.j2")
@user_required()
@shop_is_ready_required()
def user_purchases(request):
    user = request.user
    shop = request.shop  # Get the current shop from the request

    products = [p for p in request.user.products if p.shop == shop]

    invoices = (
        user.invoices.filter(Invoice.shop == shop)
        .order_by(Invoice.created_timestamp.desc())
        .all()
    )

    return {
        "products": products,
        "invoices": invoices,
        "the_title": "My Purchases",
    }


@view_config(route_name="user_offers", renderer="user_offers.j2")
@user_required()
@shop_is_ready_required()
def user_offers(request):
    """Buyer-side inbox: every offer this user has opened in this shop.

    Mirrors /u/purchases — scopes to request.shop. Open offers (pending /
    countered) sort to the top by most-recent activity; terminal offers
    follow.
    """
    from ..models.offer import OFFER_PARTY_BUYER

    user = request.user
    shop = request.shop

    if not shop.offer_enabled:
        raise HTTPNotFound()

    offers = (
        request.dbsession.query(MpsOffer)
        .filter(MpsOffer.shop_id == shop.id, MpsOffer.buyer_user_id == user.id)
        .all()
    )
    offers.sort(key=lambda o: (o.is_terminal, -(o.last_action_timestamp or 0)))

    from datetime import datetime, timezone

    def _human(ts):
        if not ts:
            return ""
        return datetime.fromtimestamp(ts / 1000, tz=timezone.utc).strftime(
            "%Y-%m-%d %H:%M UTC"
        )

    rows = []
    for o in offers:
        rows.append({
            "id": o.uuid_str,
            "product_title": o.product.title if o.product else "(removed product)",
            "state": o.state,
            "state_human": o.state_human,
            "is_open": o.is_open,
            "is_terminal": o.is_terminal,
            "current_amount": o.current_amount,
            "round_count": o.round_count,
            "last_action_human": _human(o.last_action_timestamp),
            "waiting_on_buyer": (
                o.is_open and o.current_party == OFFER_PARTY_BUYER
            ),
        })
    return {
        "the_title": "My Offers",
        "shop": shop,
        "offers": rows,
        "open_count": sum(1 for r in rows if not r["is_terminal"]),
    }


@view_config(route_name="user_bids", renderer="user_bids.j2")
@user_required()
@shop_is_ready_required()
def user_bids(request):
    """Buyer-side dashboard: every bid this user has placed on an auction
    in this shop. Latest bid per auction wins the row — older bids fold
    into history on the auction page itself.
    """
    user = request.user
    shop = request.shop

    if not shop.has_auction_products:
        raise HTTPNotFound()

    bids = (
        request.dbsession.query(MpsBid)
        .join(MpsAuction, MpsBid.auction_id == MpsAuction.id)
        .filter(
            MpsBid.bidder_user_id == user.id,
            MpsAuction.shop_id == shop.id,
        )
        .order_by(MpsBid.created_timestamp.desc())
        .all()
    )

    seen_auctions = set()
    latest_per_auction = []
    for b in bids:
        if b.auction_id in seen_auctions:
            continue
        seen_auctions.add(b.auction_id)
        latest_per_auction.append(b)

    from datetime import datetime, timezone

    def _human(ts):
        if not ts:
            return ""
        return datetime.fromtimestamp(ts / 1000, tz=timezone.utc).strftime(
            "%Y-%m-%d %H:%M UTC"
        )

    rows = []
    for b in latest_per_auction:
        a = b.auction
        rows.append({
            "auction_id": a.uuid_str,
            "product_title": a.product.title if a.product else "(removed product)",
            "bid_amount": b.amount,
            "is_winning": b.is_winning,
            "state": a.state,
            "state_human": a.state_human,
            "is_active": a.is_active,
            "is_ended": a.is_ended,
            "ends_human": _human(a.end_timestamp),
            "bid_placed_human": _human(b.created_timestamp),
        })
    return {
        "the_title": "My Bids",
        "shop": shop,
        "bids": rows,
        "active_count": sum(1 for r in rows if r["is_active"]),
    }


@view_config(route_name="user_settings", renderer="user_settings.j2")
@user_required(
    flash_msg="To view your settings, please verify your email address below.",
    flash_level="info",
    redirect_to_route_name="join-or-log-in",
)
def user_settings(request):
    user = request.user

    name = request.params.get("name", user.name).strip()
    full_name = request.params.get("full_name", user.full_name or "").strip()
    theme_id = request.params.get("theme_id", user.theme_id)

    if name and name != user.name:
        if not is_user_name_valid(name):
            msg = (
                "Sorry your display name is invalid. Please try again with only letters, numbers, spaces, dashes, and periods.",
                "error",
            )
            name = user.name
        elif not is_user_name_available(request.dbsession, name):
            msg = (
                "Sorry that display name is already taken. Please try again.",
                "error",
            )
            name = user.name
        else:
            user.name = name
            msg = ("You set your public display name.", "success")
        request.session.flash(msg)

    if full_name and full_name != user.full_name:
        user.full_name = full_name
        msg = ("You set your private full name.", "success")
        request.session.flash(msg)

    # Handle theme preference changes
    if theme_id and int(theme_id) != user.theme_id:
        user.theme_id = int(theme_id)
        theme_name = "Dark Mode" if int(theme_id) == 0 else "Light Mode"
        msg = (f"Theme preference set to {theme_name}.", "success")
        request.session.flash(msg)

    return {
        "name": name,
        "full_name": full_name,
        "theme_id": user.theme_id,
        "s3_endpoint": user.s3_endpoint or "",
        "s3_region": user.s3_region or "",
        "s3_bucket": user.s3_bucket or "",
        "s3_access_key": user.s3_access_key or "",
        "s3_secret_key": user.s3_secret_key or "",
    }


@view_config(route_name="user_storage_settings", request_method="POST")
@user_required()
def user_storage_settings(request):
    """Save or clear S3-compatible bucket credentials for artifact uploads."""
    user = request.user

    s3_endpoint = request.params.get("s3_endpoint", "").strip()
    s3_region = request.params.get("s3_region", "").strip()
    s3_bucket = request.params.get("s3_bucket", "").strip()
    s3_access_key = request.params.get("s3_access_key", "").strip()
    s3_secret_key = request.params.get("s3_secret_key", "").strip()

    # If all fields are empty, clear credentials
    if not any([s3_endpoint, s3_bucket, s3_access_key, s3_secret_key]):
        user.s3_endpoint = None
        user.s3_region = None
        user.s3_bucket = None
        user.s3_access_key = None
        user.s3_secret_key = None
        request.session.flash(("Artifact storage credentials cleared.", "success"))
        return HTTPFound("/u/settings")

    # Validate endpoint looks like a URL
    if s3_endpoint and not s3_endpoint.startswith("http"):
        request.session.flash(
            ("S3 endpoint must start with http:// or https://", "error")
        )
        return HTTPFound("/u/settings")

    # Require at minimum endpoint, bucket, and both keys
    if not (s3_endpoint and s3_bucket and s3_access_key and s3_secret_key):
        request.session.flash(
            ("Endpoint, bucket name, access key, and secret key are all required.", "error")
        )
        return HTTPFound("/u/settings")

    user.s3_endpoint = s3_endpoint
    user.s3_region = s3_region or None
    user.s3_bucket = s3_bucket
    user.s3_access_key = s3_access_key
    user.s3_secret_key = s3_secret_key

    request.session.flash(("Artifact storage settings saved.", "success"))
    return HTTPFound("/u/settings")


@view_config(route_name="user_addresses", renderer="user_addresses.j2")
@user_required()
def user_addresses(request):
    return {}


@view_config(route_name="user_address_save")
@user_required()
def user_address_save(request):
    new_address = request.params.get("new_address", None)
    if new_address:
        user_address = request.user.new_address(new_address)
        request.dbsession.add(user_address)
        request.dbsession.flush()
        msg = ("You saved that address.", "success")
        request.session.flash(msg)

    return HTTPFound("/u/addresses")


@view_config(route_name="user_address_delete")
@user_required()
def user_address_delete(request):
    address_id = request.matchdict.get("address_id")

    if request.user.active_address.uuid_str == address_id:
        msg = ("You may not delete the active address.", "error")
        request.session.flash(msg)

    else:
        for address in request.user.addresses:
            if address.uuid_str == address_id:
                request.dbsession.delete(address)
                request.dbsession.flush()
                msg = ("You deleted that address.", "success")
                request.session.flash(msg)

    return HTTPFound("/u/addresses")


@view_config(route_name="user_address_activate")
@user_required()
def user_address_activate(request):
    address_id = request.matchdict.get("address_id")

    for address in request.user.addresses:
        if address.uuid_str == address_id:
            request.user.active_address_id = address_id
            request.dbsession.add(request.user)
            request.dbsession.flush()
            msg = ("You activated that address.", "success")
            request.session.flash(msg)

    return HTTPFound("/u/addresses")


@view_config(route_name="user_profile", renderer="profile.j2")
def user_profile(request):
    """Public profile page for a user, addressed by @handle.

    Shows display name, member-since, gravatar, and the public shops the
    user owns or edits. The email address is intentionally NOT public: it
    is revealed only when (a) you are looking at your own profile, or
    (b) you are an owner/editor of the shop passed in ?shop= and the
    profile user has actually transacted with that shop (an offer or an
    invoice). This keeps the offer-history "by <name>" link useful to a
    shop operator without leaking customer emails to the world.
    """
    from datetime import datetime, timezone

    profile_user = get_user_by_name(
        request.dbsession, request.matchdict["user_name"]
    )
    if profile_user is None or profile_user.disabled:
        raise HTTPNotFound()

    viewer = request.user
    is_self = viewer is not None and viewer == profile_user

    shop = None
    shop_id = (request.params.get("shop") or "").strip()
    if shop_id:
        shop = get_shop_by_id(request.dbsession, shop_id)

    viewer_is_shop_editor = bool(
        shop is not None and viewer is not None and viewer.can_edit_shop(shop)
    )

    shop_offer_count = 0
    shop_invoice_count = 0
    if shop is not None:
        shop_offer_count = (
            request.dbsession.query(MpsOffer)
            .filter(
                MpsOffer.shop_id == shop.id,
                MpsOffer.buyer_user_id == profile_user.id,
            )
            .count()
        )
        shop_invoice_count = (
            request.dbsession.query(Invoice)
            .filter(
                Invoice.shop_id == shop.id,
                Invoice.user_id == profile_user.id,
            )
            .count()
        )
    profile_has_shop_history = bool(shop_offer_count or shop_invoice_count)

    can_see_email = is_self or (
        viewer_is_shop_editor and profile_has_shop_history
    )

    # Public shops this user owns or edits, hiding non-production shops.
    public_shops = sorted(
        {
            us.shop
            for us in profile_user.user_shops
            if us.role_id in (0, 1)
            and us.shop is not None
            and not us.shop.is_non_production
        },
        key=lambda s: (s.name or "").lower(),
    )

    member_since_human = ""
    if profile_user.created_timestamp:
        member_since_human = datetime.fromtimestamp(
            profile_user.created_timestamp / 1000, tz=timezone.utc
        ).strftime("%B %Y")

    return {
        "the_title": profile_user.display_name,
        "profile_user": profile_user,
        "display_name": profile_user.display_name,
        "handle": profile_user.name,
        "member_since": profile_user.created_timestamp,
        "member_since_human": member_since_human,
        "gravatar_url": profile_user.gravatar_url(160),
        "public_shops": public_shops,
        "shop_count": len(public_shops),
        "can_see_email": can_see_email,
        "email": profile_user.email if can_see_email else None,
        "is_self": is_self,
        "shop_context": shop,
        "shop_context_id": shop.uuid_str if shop is not None else None,
        "viewer_is_shop_editor": viewer_is_shop_editor,
        "profile_has_shop_history": profile_has_shop_history,
        "shop_offer_count": shop_offer_count,
        "shop_invoice_count": shop_invoice_count,
    }
