"""Upload blog images to Shopify Files via GraphQL staged upload."""

import logging
import time
from typing import Any

import requests

from blogs.services.shopify_admin_client import ShopifyAdminClient

logger = logging.getLogger(__name__)

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

FILE_CREATE = """
mutation fileCreate($files: [FileCreateInput!]!) {
  fileCreate(files: $files) {
    files {
      id
      fileStatus
      ... on MediaImage {
        image { url }
      }
      ... on GenericFile {
        url
      }
    }
    userErrors { field message }
  }
}
"""

FILE_URL_QUERY = """
query fileUrl($id: ID!) {
  node(id: $id) {
    ... on MediaImage {
      image { url }
    }
    ... on GenericFile {
      url
    }
  }
}
"""


class ShopifyFileAccessError(RuntimeError):
    """Raised when the Shopify token lacks file-upload scopes."""


class ShopifyFileService:
    @classmethod
    def _graphql(cls, query: str, variables: dict[str, Any] | None = None) -> dict[str, Any]:
        try:
            return ShopifyAdminClient.graphql(query, variables)
        except RuntimeError as exc:
            if "ACCESS_DENIED" in str(exc):
                raise ShopifyFileAccessError(str(exc)) from exc
            raise

    @classmethod
    def _raise_user_errors(cls, errors: list[dict[str, Any]], context: str) -> None:
        if not errors:
            return
        messages = "; ".join(
            f"{e.get('field', '')}: {e.get('message', '')}".strip(": ")
            for e in errors
        )
        raise RuntimeError(f"Shopify {context} failed: {messages}")

    @classmethod
    def _upload_to_staged_target(
        cls,
        target: dict[str, Any],
        image_bytes: bytes,
        filename: str,
        mime_type: str,
    ) -> None:
        form_fields = {p["name"]: p["value"] for p in target.get("parameters") or []}
        resp = requests.post(
            target["url"],
            data=form_fields,
            files={"file": (filename, image_bytes, mime_type)},
            timeout=120,
        )
        if resp.status_code >= 400:
            logger.error("Staged upload failed %s: %s", resp.status_code, resp.text[:500])
            resp.raise_for_status()

    @classmethod
    def _file_cdn_url(cls, file_node: dict[str, Any]) -> str:
        image = file_node.get("image") or {}
        return (image.get("url") or file_node.get("url") or "").strip()

    @classmethod
    def _wait_for_file_url(cls, file_id: str, attempts: int = 10) -> str:
        for _ in range(attempts):
            node = cls._graphql(FILE_URL_QUERY, {"id": file_id}).get("node") or {}
            url = cls._file_cdn_url(node)
            if url:
                return url
            time.sleep(1.5)
        raise RuntimeError(f"Shopify file not ready after upload: {file_id}")

    @classmethod
    def upload_image(cls, image_bytes: bytes, filename: str, alt: str = "") -> str:
        mime_type = "image/png"
        staged = cls._graphql(
            STAGED_UPLOADS_CREATE,
            {
                "input": [
                    {
                        "filename": filename,
                        "mimeType": mime_type,
                        "httpMethod": "POST",
                        "resource": "FILE",
                        "fileSize": str(len(image_bytes)),
                    }
                ]
            },
        ).get("stagedUploadsCreate") or {}
        cls._raise_user_errors(staged.get("userErrors") or [], "stagedUploadsCreate")

        targets = staged.get("stagedTargets") or []
        if not targets:
            raise RuntimeError("Shopify stagedUploadsCreate returned no targets")

        target = targets[0]
        cls._upload_to_staged_target(target, image_bytes, filename, mime_type)

        created = cls._graphql(
            FILE_CREATE,
            {
                "files": [
                    {
                        "alt": (alt or filename)[:512],
                        "contentType": "IMAGE",
                        "originalSource": target["resourceUrl"],
                    }
                ]
            },
        ).get("fileCreate") or {}
        cls._raise_user_errors(created.get("userErrors") or [], "fileCreate")

        files = created.get("files") or []
        if not files:
            raise RuntimeError("Shopify fileCreate returned no files")

        file_node = files[0]
        url = cls._file_cdn_url(file_node)
        if url:
            return url

        file_id = file_node.get("id")
        if not file_id:
            raise RuntimeError("Shopify fileCreate returned no file id")
        return cls._wait_for_file_url(file_id)
