"""MPS REST API v1 — product and content CRUD + file upload.

Products  (is_sellable=True)  → fiat or crypto priced items
Content   (is_sellable=False) → free items

File upload is a two-step flow:
  1. POST .../upload-url  → returns presigned POST URL + fields (direct to S3)
  2. Client uploads directly to S3 (file never touches MPS server)
  3. POST .../files/confirm → registers file metadata, returns CDN URL
"""

import logging
import time
import uuid

from pyramid.view import view_config

from . import api_key_required

from ...models.product import Product
from ...models.meta import now_timestamp

from ...lib.currency import dollars_to_cents

log = logging.getLogger(__name__)

# Max file size for presigned POST: 10 GB
MAX_UPLOAD_BYTES = 10 * 1024 * 1024 * 1024

VISIBILITY_MAP = {
    "public": 1,
    "private": 0,
    "unlisted": 2,
}


def _json_error(request, status, message):
    request.response.status_code = status
    return {"error": message}


def _product_to_dict(product, request):
    p_or_c = "p" if product.is_sellable else "c"
    return {
        "id": str(product.id),
        "title": product.title,
        "is_sellable": product.is_sellable,
        "visibility": {0: "private", 1: "public", 2: "unlisted"}.get(
            product.visibility, "public"
        ),
        "url": product.absolute_url(request),
        "edit_url": product.absolute_edit_url(request),
    }


# ── Create product ────────────────────────────────────────────────────────────

@view_config(route_name="api_v1_products", renderer="json", request_method="POST")
@api_key_required
def create_product(request):
    """Create a product (is_sellable=True). Requires title, description, price."""
    try:
        body = request.json_body
    except Exception:
        return _json_error(request, 400, "Invalid JSON body")

    title = (body.get("title") or "").strip()
    description = (body.get("description") or "").strip()
    price_str = (body.get("price") or "").strip()
    visibility_str = (body.get("visibility") or "public").strip()

    if not title:
        return _json_error(request, 400, "title is required")
    if not description:
        return _json_error(request, 400, "description is required")
    if not price_str:
        return _json_error(request, 400, "price is required for products")

    try:
        price_float = float(price_str)
        if price_float < 0:
            raise ValueError("price must be >= 0")
    except ValueError as exc:
        return _json_error(request, 400, f"Invalid price: {exc}")

    visibility = VISIBILITY_MAP.get(visibility_str, 1)

    shop = request.api_shop

    product = Product(title, description)
    product.shop = shop
    product.is_sellable = True
    product.visibility = visibility

    if product.error_message:
        return _json_error(request, 400, product.error_message)

    if price_float > 0:
        product_price = product.set_price(price_float)
        request.dbsession.add(product_price)

    request.dbsession.add(product)
    request.dbsession.flush()

    request.response.status_code = 201
    return _product_to_dict(product, request)


# ── Create content ────────────────────────────────────────────────────────────

@view_config(route_name="api_v1_content", renderer="json", request_method="POST")
@api_key_required
def create_content(request):
    """Create a content item (is_sellable=False). Requires title, description."""
    try:
        body = request.json_body
    except Exception:
        return _json_error(request, 400, "Invalid JSON body")

    title = (body.get("title") or "").strip()
    description = (body.get("description") or "").strip()
    visibility_str = (body.get("visibility") or "public").strip()

    if not title:
        return _json_error(request, 400, "title is required")
    if not description:
        return _json_error(request, 400, "description is required")

    visibility = VISIBILITY_MAP.get(visibility_str, 1)
    shop = request.api_shop

    product = Product(title, description)
    product.shop = shop
    product.is_sellable = False
    product.visibility = visibility

    if product.error_message:
        return _json_error(request, 400, product.error_message)

    request.dbsession.add(product)
    request.dbsession.flush()

    request.response.status_code = 201
    return _product_to_dict(product, request)


# ── Get product ───────────────────────────────────────────────────────────────

@view_config(route_name="api_v1_product", renderer="json", request_method="GET")
@api_key_required
def get_product(request):
    product_id = request.matchdict["product_id"]
    product = _get_item(request, product_id, sellable=True)
    if product is None:
        return _json_error(request, 404, "Product not found")
    return _product_to_dict(product, request)


# ── Get content ───────────────────────────────────────────────────────────────

@view_config(route_name="api_v1_content_item", renderer="json", request_method="GET")
@api_key_required
def get_content(request):
    content_id = request.matchdict["content_id"]
    product = _get_item(request, content_id, sellable=False)
    if product is None:
        return _json_error(request, 404, "Content not found")
    return _product_to_dict(product, request)


# ── Upload URL — product ──────────────────────────────────────────────────────

@view_config(route_name="api_v1_product_upload_url", renderer="json", request_method="POST")
@api_key_required
def product_upload_url(request):
    product_id = request.matchdict["product_id"]
    product = _get_item(request, product_id, sellable=True)
    if product is None:
        return _json_error(request, 404, "Product not found")
    return _generate_upload_url(request, product)


# ── Upload URL — content ──────────────────────────────────────────────────────

@view_config(route_name="api_v1_content_upload_url", renderer="json", request_method="POST")
@api_key_required
def content_upload_url(request):
    content_id = request.matchdict["content_id"]
    product = _get_item(request, content_id, sellable=False)
    if product is None:
        return _json_error(request, 404, "Content not found")
    return _generate_upload_url(request, product)


# ── Confirm upload — product ──────────────────────────────────────────────────

@view_config(route_name="api_v1_product_confirm", renderer="json", request_method="POST")
@api_key_required
def product_confirm(request):
    product_id = request.matchdict["product_id"]
    product = _get_item(request, product_id, sellable=True)
    if product is None:
        return _json_error(request, 404, "Product not found")
    return _confirm_upload(request, product)


# ── Confirm upload — content ──────────────────────────────────────────────────

@view_config(route_name="api_v1_content_confirm", renderer="json", request_method="POST")
@api_key_required
def content_confirm(request):
    content_id = request.matchdict["content_id"]
    product = _get_item(request, content_id, sellable=False)
    if product is None:
        return _json_error(request, 404, "Content not found")
    return _confirm_upload(request, product)


# ── Helpers ───────────────────────────────────────────────────────────────────

def _get_item(request, item_id, sellable):
    """Look up a product/content belonging to the authenticated shop."""
    from ...models.product import get_object_by_id as _get
    try:
        product = request.dbsession.query(Product).filter_by(id=item_id).first()
    except Exception:
        return None
    if product is None:
        return None
    if str(product.shop_id) != str(request.api_shop.id):
        return None
    if product.is_sellable != sellable:
        return None
    return product


def _generate_upload_url(request, product):
    """Generate a presigned POST URL for direct S3 upload."""
    try:
        body = request.json_body
    except Exception:
        return _json_error(request, 400, "Invalid JSON body")

    filename = (body.get("filename") or "").strip()
    content_type = (body.get("content_type") or "application/octet-stream").strip()

    if not filename:
        return _json_error(request, 400, "filename is required")

    # Sanitize filename
    safe_filename = "".join(
        c for c in filename if c.isalnum() or c in ".-_"
    ) or "upload"

    # Tmp key: tmp/{shop_id}/{product_id}/{timestamp}/{safe_filename}
    tmp_key = (
        f"tmp/{product.shop_id}/{product.id}"
        f"/{int(time.time())}/{safe_filename}"
    )

    shop = request.api_shop
    client = request.shop_uploads_client
    bucket = request.shop_bucket_name

    try:
        presigned = client.generate_presigned_post(
            Bucket=bucket,
            Key=tmp_key,
            ExpiresIn=3600,
            Conditions=[
                ["content-length-range", 1, MAX_UPLOAD_BYTES],
                {"Content-Type": content_type},
            ],
            Fields={"Content-Type": content_type},
        )
    except Exception as exc:
        log.warning("generate_presigned_post failed for product %s: %s", product.id, exc)
        return _json_error(request, 500, "Failed to generate upload URL")

    p_or_c = "products" if product.is_sellable else "content"
    return {
        "upload_url": presigned["url"],
        "fields": presigned["fields"],
        "key": tmp_key,
        "confirm_path": f"/api/v1/{p_or_c}/{product.id}/files/confirm",
    }


def _confirm_upload(request, product):
    """Copy uploaded file from tmp key to product S3 path, register metadata."""
    try:
        body = request.json_body
    except Exception:
        return _json_error(request, 400, "Invalid JSON body")

    tmp_key = (body.get("key") or "").strip()
    filename = (body.get("filename") or "").strip()

    if not tmp_key:
        return _json_error(request, 400, "key is required")
    if not filename:
        return _json_error(request, 400, "filename is required")

    # Validate tmp_key belongs to this product (not path traversal)
    expected_prefix = f"tmp/{product.shop_id}/{product.id}/"
    if not tmp_key.startswith(expected_prefix):
        return _json_error(request, 400, "key does not belong to this product")

    # Sanitize filename for S3 key
    safe_filename = "".join(
        c for c in filename if c.isalnum() or c in ".-_"
    ) or "upload"

    # Final key: {shop_id}/{product_id}/product.{safe_filename}
    final_key = f"{product.s3_path}/product.{safe_filename}"

    client = request.shop_uploads_client
    bucket = request.shop_bucket_name

    # Verify tmp object exists
    try:
        client.head_object(Bucket=bucket, Key=tmp_key)
    except Exception:
        return _json_error(request, 400, "Uploaded file not found — upload may have failed")

    # Copy to final location
    try:
        client.copy_object(
            CopySource={"Bucket": bucket, "Key": tmp_key},
            Bucket=bucket,
            Key=final_key,
        )
    except Exception as exc:
        log.warning("copy_object failed for product %s: %s", product.id, exc)
        return _json_error(request, 500, "Failed to register uploaded file")

    # Register metadata on product
    product.store_file_metadata(final_key)
    request.dbsession.add(product)

    # Delete tmp object
    try:
        client.delete_object(Bucket=bucket, Key=tmp_key)
    except Exception as exc:
        log.warning("delete tmp key failed for product %s: %s", product.id, exc)

    # Build CDN URL
    cdn_endpoint = request.shop_cdn_endpoint
    cdn_url = f"{cdn_endpoint.rstrip('/')}/{final_key}"

    return {"cdn_url": cdn_url}
