"""Fetch external images (e.g. R2) and upload to Shopify via staged uploads."""

from __future__ import annotations

import logging
import mimetypes
from typing import Any, Dict, Optional
from urllib.parse import unquote, urlparse

import requests

from db.image_urls import normalize_external_image_url, prepare_image_urls

logger = logging.getLogger(__name__)

STAGED_UPLOADS_CREATE = """
mutation stagedUploadsCreate($input: [StagedUploadInput!]!) {
  stagedUploadsCreate(input: $input) {
    stagedTargets {
      url
      resourceUrl
      parameters { name value }
    }
    userErrors { field message }
  }
}
"""


def _filename_from_url(url: str, content_type: Optional[str] = None) -> str:
    path = unquote(urlparse(url).path or "")
    filename = path.rsplit("/", 1)[-1] if path else ""
    filename = filename.strip() or "image.jpg"
    if "." not in filename:
        ext = mimetypes.guess_extension((content_type or "").split(";")[0].strip()) or ".jpg"
        filename = f"{filename}{ext}"
    return filename


def stage_image_bytes(
    client,
    content: bytes,
    *,
    filename: str,
    mime_type: str,
) -> str:
    """Upload bytes to Shopify staging; return resourceUrl for productCreateMedia."""
    mime = (mime_type or "image/jpeg").split(";")[0].strip() or "image/jpeg"
    data = client.graphql(
        STAGED_UPLOADS_CREATE,
        {
            "input": [
                {
                    "filename": filename,
                    "mimeType": mime,
                    "httpMethod": "POST",
                    "resource": "PRODUCT_IMAGE",
                    "fileSize": str(len(content)),
                }
            ]
        },
    )
    block = data.get("stagedUploadsCreate") or {}
    errors = block.get("userErrors") or []
    if errors:
        raise RuntimeError(f"stagedUploadsCreate userErrors: {errors}")
    targets = block.get("stagedTargets") or []
    if not targets:
        raise RuntimeError("stagedUploadsCreate returned no stagedTargets")
    target = targets[0]
    upload_url = str(target.get("url") or "")
    resource_url = str(target.get("resourceUrl") or "")
    if not upload_url or not resource_url:
        raise RuntimeError(f"stagedUploadsCreate incomplete target: {target}")

    form: Dict[str, Any] = {p["name"]: p["value"] for p in (target.get("parameters") or [])}
    response = requests.post(
        upload_url,
        data=form,
        files={"file": (filename, content, mime)},
        timeout=120,
    )
    response.raise_for_status()
    return resource_url


def stage_file_bytes(
    client,
    content: bytes,
    *,
    filename: str,
    mime_type: str,
) -> str:
    """Upload image/PDF bytes to Shopify staging for ``fileCreate``."""
    mime = (mime_type or "application/octet-stream").split(";", 1)[0].strip()
    data = client.graphql(
        STAGED_UPLOADS_CREATE,
        {
            "input": [
                {
                    "filename": filename,
                    "mimeType": mime,
                    "httpMethod": "POST",
                    "resource": "IMAGE" if mime.startswith("image/") else "FILE",
                    "fileSize": str(len(content)),
                }
            ]
        },
    )
    block = data.get("stagedUploadsCreate") or {}
    errors = block.get("userErrors") or []
    if errors:
        raise RuntimeError(f"stagedUploadsCreate userErrors: {errors}")
    targets = block.get("stagedTargets") or []
    if not targets:
        raise RuntimeError("stagedUploadsCreate returned no stagedTargets")
    target = targets[0]
    upload_url = str(target.get("url") or "")
    resource_url = str(target.get("resourceUrl") or "")
    if not upload_url or not resource_url:
        raise RuntimeError(f"stagedUploadsCreate incomplete target: {target}")
    form: Dict[str, Any] = {p["name"]: p["value"] for p in (target.get("parameters") or [])}
    response = requests.post(
        upload_url,
        data=form,
        files={"file": (filename, content, mime)},
        timeout=120,
    )
    response.raise_for_status()
    return resource_url


def fetch_and_stage_image(client, source_url: str) -> str:
    """Download an external image and stage it in Shopify (fallback when direct URL import fails)."""
    normalized = normalize_external_image_url(source_url)
    response = requests.get(normalized, timeout=120, allow_redirects=True)
    response.raise_for_status()
    content = response.content
    if not content:
        raise RuntimeError(f"empty image body from {normalized}")
    content_type = response.headers.get("Content-Type")
    filename = _filename_from_url(normalized, content_type)
    mime = (content_type or mimetypes.guess_type(filename)[0] or "image/jpeg").split(";")[0]
    logger.info("shopify image staging: fetched %s bytes from %s", len(content), normalized)
    return stage_image_bytes(client, content, filename=filename, mime_type=mime)


def is_invalid_image_url_error(exc: Exception) -> bool:
    return "Image URL is invalid" in str(exc)
