"""Webhook and Shopify integration views."""

import json
import logging
import os
import hashlib
import hmac
import base64
import binascii
from urllib.parse import parse_qs

import shopify

from django.conf import settings
from django.http import (
    JsonResponse,
    HttpResponse,
    HttpResponseBadRequest,
    HttpResponseRedirect,
)
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt

from webhooks.models import ShopifyStore
from webhooks.services import WebhookService


logger = logging.getLogger("webhooks")


# =========================================================
# SHOPIFY WEBHOOK
# =========================================================

@method_decorator(csrf_exempt, name="dispatch")
class ShopifyWebhookView(View):
    """
    POST /webhooks/shopify/<topic>/
    topic e.g. orders-create, products-update
    """

    def _is_valid_hmac(self, request) -> bool:
        secret = (settings.SHOPIFY_API_SECRET or "").strip()
        if not secret:
            return True
        header_hmac = (request.headers.get("X-Shopify-Hmac-Sha256") or "").strip()
        if not header_hmac:
            return False
        digest = hmac.new(
            secret.encode("utf-8"),
            request.body,
            hashlib.sha256,
        ).digest()
        computed = base64.b64encode(digest).decode("utf-8")
        return hmac.compare_digest(computed, header_hmac)

    def post(self, request, topic: str):
        if not self._is_valid_hmac(request):
            return JsonResponse({"ok": False, "error": "invalid_hmac"}, status=401)

        try:
            payload = json.loads(request.body)
            logger.info("Shopify webhook received | topic=%s | payload=%s", topic, payload)
        except json.JSONDecodeError:
            logger.error("Shopify webhook invalid JSON | topic=%s | body=%s", topic, request.body)
            return JsonResponse({"ok": False, "error": "invalid_json"}, status=400)

        topic_normalized = topic.replace("-", "/")

        if topic_normalized in ("products/update", "products/create"):
            result = WebhookService.handle_product_updated(payload)
        elif topic_normalized == "products/delete":
            result = WebhookService.handle_product_deleted(payload)
        elif topic_normalized == "refunds/create":
            webhook_id = request.headers.get("X-Shopify-Webhook-Id") or ""
            try:
                result = WebhookService.handle_refund_created(
                    payload,
                    webhook_id=webhook_id,
                )
            except Exception as exc:
                logger.exception("Refund webhook failed: %s", exc)
                result = {"ok": False, "error": str(exc)}
        elif topic_normalized in ("fulfillments/create", "fulfillments/update"):
            webhook_id = request.headers.get("X-Shopify-Webhook-Id") or ""
            try:
                result = WebhookService.handle_fulfillment_created_or_updated(
                    payload,
                    webhook_id=webhook_id,
                    topic=topic_normalized,
                )
            except Exception as exc:
                logger.exception("Fulfillment webhook failed: %s", exc)
                result = {"ok": False, "error": str(exc)}
        else:
            result = {"ok": True, "skipped": True, "topic": topic}

        status = 200 if result.get("ok") else 400
        return JsonResponse(result, status=status)

# =========================================================
# SHOPIFY INSTALL FLOW
# =========================================================

@method_decorator(csrf_exempt, name="dispatch")
class ShopifyInstallView(View):
    """
    GET /webhooks/shopify/install/?shop=store.myshopify.com
    """

    def get(self, request):
        shop = request.GET.get("shop")

        if not shop:
            return HttpResponseBadRequest("Missing 'shop' query parameter.")

        if not settings.SHOPIFY_API_KEY or not settings.SHOPIFY_API_SECRET:
            return HttpResponseBadRequest("Shopify API credentials not configured.")

        shopify.Session.setup(
            api_key=settings.SHOPIFY_API_KEY,
            secret=settings.SHOPIFY_API_SECRET,
        )

        api_version = settings.SHOPIFY_API_VERSION

        state = binascii.b2a_hex(os.urandom(15)).decode("utf-8")

        request.session["shopify_oauth_state"] = state
        request.session["shopify_oauth_shop"] = shop

        base_url = getattr(settings, "HOST_URL", "") or request.build_absolute_uri("/")[:-1]
        redirect_uri = f"{base_url}/webhooks/shopify/callback/"

        # Subset of Partner app scopes; reinstall OAuth after any change.
        scopes = [
            "read_products",
            "write_products",  # products, metafields, product webhooks
            "read_orders",
            "write_orders",  # Digistore IPN → orderCreate
            "read_customers",
            "write_customers",  # bulk customer tag updates
            "read_fulfillments",  # fulfillments/create + fulfillments/update webhooks
            "read_content",
            "write_content",  # blog articles
            "read_files",
            "write_files",  # blog images → Shopify CDN
        ]

        session = shopify.Session(shop, api_version)
        auth_url = session.create_permission_url(scopes, redirect_uri, state)

        return HttpResponseRedirect(auth_url)


# =========================================================
# SHOPIFY CALLBACK
# =========================================================

@method_decorator(csrf_exempt, name="dispatch")
class ShopifyCallbackView(View):
    """
    GET /webhooks/shopify/callback/
    """

    def get(self, request):
        shop = request.GET.get("shop")
        state = request.GET.get("state")
        params = request.GET.dict()

        if not shop or not state:
            return HttpResponseBadRequest("Missing 'shop' or 'state' in callback.")

        expected_state = request.session.get("shopify_oauth_state")
        expected_shop = request.session.get("shopify_oauth_shop")

        if not expected_state or state != expected_state or expected_shop != shop:
            return HttpResponseBadRequest("Invalid OAuth state.")

        if not settings.SHOPIFY_API_KEY or not settings.SHOPIFY_API_SECRET:
            return HttpResponseBadRequest("Shopify API credentials not configured.")

        shopify.Session.setup(
            api_key=settings.SHOPIFY_API_KEY,
            secret=settings.SHOPIFY_API_SECRET,
        )

        session = shopify.Session(shop, settings.SHOPIFY_API_VERSION)

        try:
            access_token = session.request_token(params)
        except Exception as exc:
            return HttpResponseBadRequest(f"Failed to exchange code for token: {exc}")

        store, created = ShopifyStore.objects.update_or_create(
            shop_domain=shop,
            defaults={"access_token": access_token},
        )

        request.session.pop("shopify_oauth_state", None)
        request.session.pop("shopify_oauth_shop", None)

        return JsonResponse(
            {
                "ok": True,
                "shop_domain": store.shop_domain,
                "created": created,
            }
        )


# =========================================================
# DIGISTORE IPN
# =========================================================

@method_decorator(csrf_exempt, name="dispatch")
class DigistoreIPNView(View):
    """
    POST /webhooks/digistore/ipn/
    """

    def get(self, request):
        query_params = request.GET.dict()
        logger.info(
            "Digistore IPN GET | query=%s",
            query_params,
        )
        return JsonResponse({"ok": True, "method": "GET"})

    def post(self, request):
        try:
            body_text = request.body.decode("utf-8") if request.body else ""
        except UnicodeDecodeError:
            body_text = "<non-utf8-body>"

        query_params = request.GET.dict()
        post_data = request.POST.dict()

        if not post_data and body_text and body_text != "<non-utf8-body>":
            parsed = parse_qs(body_text, keep_blank_values=True)
            post_data = {
                k: v[0] if isinstance(v, list) and v else ""
                for k, v in parsed.items()
            }

        event = post_data.get("event") or ""
        logger.info(
            "Digistore IPN POST | event=%s | query=%s | payload=%s",
            event,
            query_params,
            post_data,
        )

        # ---------- SIGNATURE VALIDATION ----------
        secret = os.getenv("IPN_SHA_PASSPHRASE") or ""

        if secret:
            received_sig = (
                (post_data.get("sha_sign") or post_data.get("SHASIGN") or "")
                .strip()
                .upper()
            )

            items = []
            for key in sorted(post_data.keys()):
                if key in ("sha_sign", "SHASIGN"):
                    continue

                value = post_data.get(key)

                if value is None or value == "" or value is False:
                    continue

                items.append(f"{key}={value}{secret}")

            sha_string = "".join(items)

            computed_sig = hashlib.sha512(
                sha_string.encode("utf-8")
            ).hexdigest().upper()

            if not received_sig or computed_sig != received_sig:
                logger.warning("Digistore IPN invalid signature")
                return HttpResponse("ERROR: invalid sha signature")

        if event == "connection_test":
            return HttpResponse("OK")

        if event == "on_payment":
            try:
                from orders.services import OrderService

                digistore_order_id = str(
                    post_data.get("order_id") or ""
                ).strip()

                if not digistore_order_id:
                    return HttpResponse("ERROR: missing_order_id")

                result = OrderService.sync_order_to_shopify(
                    digistore_order_id,
                    ipn_data=post_data,
                    fetch_purchase=True,
                )

                if result.get("result") == "failed":
                    return HttpResponse("ERROR: shopify_order_failed")

            except Exception as exc:
                logger.exception(
                    "Digistore IPN on_payment failed: %s",
                    exc,
                )
                try:
                    from orders.services import OrderService as _OrderService

                    oid = str(post_data.get("order_id") or "").strip()
                    if oid:
                        _OrderService.set_order_sync_error(oid, str(exc))
                except Exception:
                    pass
                return HttpResponse("ERROR: shopify_order_failed")

            return HttpResponse("OK")

        return HttpResponse("OK")