"""Publish blog articles to Shopify Admin REST API."""

import base64
import logging
import os
from typing import Any

import requests
from django.utils import timezone

from blogs.models import BlogArticle, BlogGenerationLog, BlogSettings
from blogs.services.blog_image_storage import BlogImageStorage
from blogs.services.image_service import BlogImageService
from blogs.services.shopify_admin_client import ShopifyAdminClient

logger = logging.getLogger(__name__)

SEO_METAFIELD_SPECS = (
    ("global", "title_tag"),
    ("global", "description_tag"),
)


class ShopifyBlogService:
    _client = ShopifyAdminClient

    @classmethod
    def _headers(cls) -> dict[str, str]:
        return cls._client.headers()

    @classmethod
    def _base_url(cls) -> str:
        return cls._client.base_url()

    @classmethod
    def _api_version(cls) -> str:
        return cls._client.api_version()

    @classmethod
    def list_blogs(cls) -> list[dict[str, Any]]:
        resp = requests.get(
            cls._client.admin_url("blogs.json"),
            headers=cls._headers(),
            timeout=30,
        )
        resp.raise_for_status()
        return resp.json().get("blogs") or []

    @classmethod
    def list_articles(cls, blog_id: str, limit: int = 50) -> list[dict[str, Any]]:
        resp = requests.get(
            cls._client.admin_url(f"blogs/{blog_id}/articles.json?limit={limit}"),
            headers=cls._headers(),
            timeout=30,
        )
        resp.raise_for_status()
        return resp.json().get("articles") or []

    @classmethod
    def test_connection(cls, blog_id: str) -> bool:
        resp = requests.get(
            cls._client.admin_url(f"blogs/{blog_id}/articles.json?limit=1"),
            headers=cls._headers(),
            timeout=30,
        )
        return resp.status_code == 200

    @classmethod
    def _shopify_author_name(cls) -> str:
        settings = BlogSettings.load()
        author = (settings.blog_author_name or settings.store_name or "").strip()
        return author

    @classmethod
    def _build_article_payload(cls, article: BlogArticle, published: bool = True) -> dict:
        tags = article.tags or []
        tags_str = ", ".join(str(t) for t in tags) if isinstance(tags, list) else str(tags)

        metafields = [
            {
                "namespace": "seo",
                "key": "title",
                "value": article.meta_title or article.title,
                "type": "single_line_text_field",
            },
            {
                "namespace": "seo",
                "key": "description",
                "value": article.meta_description or "",
                "type": "single_line_text_field",
            },
        ]
        if article.target_keyword:
            metafields.append(
                {
                    "namespace": "custom",
                    "key": "target_keyword",
                    "value": article.target_keyword,
                    "type": "single_line_text_field",
                }
            )

        payload: dict[str, Any] = {
            "title": article.title,
            "body_html": article.content,
            "handle": article.slug,
            "tags": tags_str,
            "published": published,
            "summary_html": (
                f"<p>{article.meta_description}</p>" if article.meta_description else ""
            ),
            "metafields": metafields,
        }
        author = cls._shopify_author_name()
        if author:
            payload["author"] = author
        featured = cls._featured_image_payload(article)
        if featured:
            payload["image"] = featured
        return {"article": payload}

    @classmethod
    def _header_image(cls, article: BlogArticle):
        return article.images.filter(
            position="header", generation_status="completed"
        ).first()

    @classmethod
    def _featured_image_payload(cls, article: BlogArticle) -> dict[str, str] | None:
        header = cls._header_image(article)
        if header and header.local_file:
            try:
                return {
                    "attachment": base64.b64encode(
                        BlogImageStorage.read_bytes(header.local_file)
                    ).decode("ascii"),
                    "alt": article.title,
                }
            except FileNotFoundError:
                logger.warning(
                    "Staged header missing for article=%s path=%s",
                    article.pk,
                    header.local_file,
                )

        url = (article.featured_image_url or "").strip()
        if not url and header:
            url = (header.image_url or "").strip()
        if url:
            return {"src": url, "alt": article.title}
        return None

    @classmethod
    def _articles_url(cls, blog_id: str, shopify_article_id: str | None = None) -> str:
        path = f"blogs/{blog_id}/articles"
        if shopify_article_id:
            path = f"{path}/{shopify_article_id}"
        return cls._client.admin_url(f"{path}.json")

    @classmethod
    def _send_article_request(
        cls,
        method: str,
        url: str,
        body: dict[str, Any],
    ) -> requests.Response:
        resp = requests.request(
            method, url, headers=cls._headers(), json=body, timeout=120
        )
        if resp.status_code >= 400 and body.get("article", {}).get("metafields"):
            body_no_meta = {"article": dict(body["article"])}
            body_no_meta["article"].pop("metafields", None)
            resp = requests.request(
                method, url, headers=cls._headers(), json=body_no_meta, timeout=120
            )
        return resp

    @classmethod
    def publish_article(cls, article_id: int, published: bool = True) -> dict[str, Any]:
        article = BlogArticle.objects.get(pk=article_id)
        blog_settings = BlogSettings.load()
        blog_id = (blog_settings.shopify_blog_id or "").strip()
        if not blog_id:
            raise RuntimeError("shopify_blog_id not configured in Blog settings")

        BlogGenerationLog.objects.create(
            article=article, step="posting", status="started", details={}
        )
        article.status = BlogArticle.STATUS_PUBLISHING
        article.save(update_fields=["status", "updated_at"])

        if blog_settings.include_images:
            BlogImageService.upload_staged_images_to_shopify(article)
            article.refresh_from_db()

        shopify_article_id = (article.shopify_article_id or "").strip()
        is_update = bool(shopify_article_id)
        url = cls._articles_url(blog_id, shopify_article_id or None)
        body = cls._build_article_payload(article, published=published)
        method = "PUT" if is_update else "POST"

        try:
            resp = cls._send_article_request(method, url, body)
            resp.raise_for_status()
            shopify_article = resp.json().get("article") or {}

            if not is_update:
                article.shopify_article_id = str(shopify_article.get("id", ""))
            handle = shopify_article.get("handle") or article.slug
            store_url = (os.getenv("STORE_URL") or cls._base_url()).strip()
            if not store_url.startswith("http"):
                store_url = f"https://{store_url}"
            article.shopify_article_url = f"{store_url.rstrip('/')}/blogs/news/{handle}"
            article.shopify_metafields = body["article"].get("metafields", [])
            article.status = BlogArticle.STATUS_PUBLISHED
            article.posted_at = timezone.now()
            article.generation_error = ""
            article.save()

            cls.sync_article_seo(article, blog_id)
            cls._cleanup_staged_images(article)

            BlogGenerationLog.objects.create(
                article=article,
                step="posting",
                status="completed",
                details={"shopify_article_id": article.shopify_article_id},
            )
            return {
                "shopify_article_id": article.shopify_article_id,
                "shopify_article_url": article.shopify_article_url,
            }
        except Exception as exc:
            logger.exception("Shopify publish failed article_id=%s", article_id)
            article.status = BlogArticle.STATUS_FAILED
            article.generation_error = str(exc)
            article.save(update_fields=["status", "generation_error", "updated_at"])
            BlogGenerationLog.objects.create(
                article=article,
                step="posting",
                status="failed",
                details={"error": str(exc)},
            )
            raise

    @classmethod
    def _cleanup_staged_images(cls, article: BlogArticle) -> None:
        """Remove local staging files after a successful publish."""
        for img in article.images.exclude(local_file=""):
            BlogImageStorage.delete(img.local_file)
            img.local_file = ""
            img.save(update_fields=["local_file"])

    @classmethod
    def _article_metafields_url(cls, blog_id: str, article_id: str) -> str:
        return cls._client.admin_url(
            f"blogs/{blog_id}/articles/{article_id}/metafields.json"
        )

    @classmethod
    def _list_article_metafields(cls, blog_id: str, article_id: str) -> list[dict[str, Any]]:
        resp = requests.get(
            cls._article_metafields_url(blog_id, article_id),
            headers=cls._headers(),
            timeout=30,
        )
        resp.raise_for_status()
        return resp.json().get("metafields") or []

    @classmethod
    def _upsert_article_metafield(
        cls,
        blog_id: str,
        article_id: str,
        namespace: str,
        key: str,
        value: str,
        existing: dict[tuple[str, str], dict],
    ) -> None:
        if not value:
            return
        payload = {
            "metafield": {
                "namespace": namespace,
                "key": key,
                "value": value,
                "type": "single_line_text_field",
            }
        }
        found = existing.get((namespace, key))
        if found and found.get("id"):
            url = cls._client.admin_url(f"metafields/{found['id']}.json")
            resp = requests.put(url, headers=cls._headers(), json=payload, timeout=30)
        else:
            resp = requests.post(
                cls._article_metafields_url(blog_id, article_id),
                headers=cls._headers(),
                json=payload,
                timeout=30,
            )
        if resp.status_code >= 400:
            logger.warning(
                "metafield %s.%s failed status=%s body=%s",
                namespace,
                key,
                resp.status_code,
                resp.text[:300],
            )
            resp.raise_for_status()

    @classmethod
    def delete_article(cls, article: BlogArticle) -> bool:
        shopify_id = (article.shopify_article_id or "").strip()
        if not shopify_id:
            return False

        blog_id = (BlogSettings.load().shopify_blog_id or "").strip()
        if not blog_id:
            raise RuntimeError("shopify_blog_id not configured in Blog settings")

        url = cls._articles_url(blog_id, shopify_id)
        resp = requests.delete(url, headers=cls._headers(), timeout=30)
        if resp.status_code == 404:
            logger.info(
                "Shopify article already deleted blog=%s article=%s",
                blog_id,
                shopify_id,
            )
            return True
        if resp.status_code >= 400:
            logger.error(
                "Shopify delete failed status=%s body=%s",
                resp.status_code,
                resp.text[:500],
            )
            resp.raise_for_status()
        logger.info(
            "Shopify article deleted blog=%s article=%s django_id=%s",
            blog_id,
            shopify_id,
            article.pk,
        )
        return True

    @classmethod
    def sync_article_seo(cls, article: BlogArticle, blog_id: str | None = None) -> None:
        if not article.shopify_article_id:
            return
        blog_id = (blog_id or BlogSettings.load().shopify_blog_id or "").strip()
        if not blog_id:
            raise RuntimeError("shopify_blog_id not configured in Blog settings")

        values = {
            "title_tag": (article.meta_title or article.title or "").strip(),
            "description_tag": (article.meta_description or "").strip(),
        }
        existing_list = cls._list_article_metafields(blog_id, article.shopify_article_id)
        existing = {
            (m.get("namespace"), m.get("key")): m
            for m in existing_list
            if m.get("namespace") and m.get("key")
        }
        for namespace, key in SEO_METAFIELD_SPECS:
            cls._upsert_article_metafield(
                blog_id,
                article.shopify_article_id,
                namespace,
                key,
                values.get(key, ""),
                existing,
            )

        article.shopify_metafields = [
            {
                "namespace": ns,
                "key": key,
                "value": values.get(key, ""),
                "type": "single_line_text_field",
            }
            for ns, key in SEO_METAFIELD_SPECS
            if values.get(key)
        ]
        article.save(update_fields=["shopify_metafields", "updated_at"])
