import logging

from django.apps import apps
from django.http import HttpResponse
from django.urls import reverse
from django.shortcuts import redirect
from django.conf import settings

logger = logging.getLogger(__name__)

from shopify import ApiAccess, Session, session_token
from shopify.utils import shop_url

from .utils import get_auth_shop, get_shop, get_shop_model


def shopify_embed(func):
    """Add CSP frame-ancestors header for embedded app views."""
    def wrapper(request, **kwargs):
        shop = request.GET.get("shop")
        response = func(request, **kwargs)
        if shop:
            sanitized = shop_url.sanitize_shop_domain(shop)
            if sanitized:
                ancestors = (
                    f"frame-ancestors https://{sanitized} https://admin.shopify.com"
                )
                response["Content-Security-Policy"] = ancestors

        return response

    return wrapper


def get_authorization_header_from_request(request):
    """Extract the session token from ShopifyAuth, Authorization header, or id_token query param."""
    if request.META.get("HTTP_SHOPIFYAUTH"):
        return request.META.get("HTTP_SHOPIFYAUTH")

    elif request.META.get("HTTP_AUTHORIZATION"):
        return request.META.get("HTTP_AUTHORIZATION")

    elif request.GET.get("id_token"):
        return "Bearer " + request.GET.get("id_token")

    else:
        return None


def get_shopify_domain_from_request(request):
    """Extract shop domain from ShopShopifyDomain header or shop query param."""
    return request.META.get("HTTP_SHOPSHOPIFYDOMAIN") or request.GET.get("shop")


def enrich_response(response, request):
    """Add CORS headers for Shopify extension requests."""
    # Always allow extensions.shopifycdn.com for now
    response["Access-Control-Allow-Origin"] = "https://extensions.shopifycdn.com"

    response["Access-Control-Allow-Headers"] = (
        "Content-Type, Authorization, X-Requested-With"
    )
    response["Access-Control-Allow-Credentials"] = "true"

    return response


def get_shop_session_options_response(request):
    """Build a CORS preflight response for OPTIONS requests."""
    options_response = HttpResponse()
    options_response = enrich_response(options_response, request)
    return options_response


def _get_raw_session_token(authorization_header):
    """Extract the raw JWT string from the Authorization header."""
    if authorization_header.startswith("Bearer "):
        return authorization_header[7:]
    return authorization_header


def shop_session(func):
    """Validate the Shopify session token (JWT) from the request.

    Injects shop, shopify_domain, and shopify_user_id into view kwargs.
    With SHOPIFY_TOKEN_EXCHANGE enabled, automatically exchanges the session
    token for an access token if the shop has none stored.
    """
    def wrapper(*args, **kwargs):

        # allow all if method is OPTIONS
        request = args[0]
        if request.method == "OPTIONS":
            return get_shop_session_options_response(request)

        try:
            request = args[0]
            authorization_header = get_authorization_header_from_request(request)
            if not authorization_header:
                return HttpResponse(status=401, content="Missing authorization header")

            shop_shopify_domain = get_shopify_domain_from_request(request)
            auth_shop = get_auth_shop(shop_shopify_domain)

            decoded_session_token = session_token.decode_from_header(
                authorization_header=authorization_header,
                api_key=auth_shop.shopify_app_api_key,
                secret=auth_shop.shopify_app_api_secret,
            )

            shopify_domain = decoded_session_token.get("dest")
            shopify_domain = shopify_domain.removeprefix("https://")

            check_shop_domain(request, kwargs, shopify_domain)

            token_exchange_enabled = apps.get_app_config(
                "shopify_app"
            ).SHOPIFY_TOKEN_EXCHANGE

            if token_exchange_enabled:
                from .services.token_exchange import (
                    ensure_access_token,
                    track_user_login,
                )

                raw_token = _get_raw_session_token(authorization_header)
                shop = ensure_access_token(shopify_domain, raw_token, request=request)

                if not shop:
                    return HttpResponse(status=401, content="Token exchange failed")

                kwargs["shop"] = shop
                request.shop = shop

                track_user_login(shop, decoded_session_token["sub"], raw_token)
            else:
                check_shop_known(request, kwargs)

            kwargs["shopify_user_id"] = decoded_session_token["sub"]

            response = func(*args, **kwargs)
            response = enrich_response(response, request)

            return response
        except session_token.SessionTokenError as e:
            logger.warning("Session token validation failed: %s", e)
            return HttpResponse(status=401, content="Invalid session token")

    return wrapper


def shopify_session(session_token):
    """Create a temporary shopify.Session from a decoded session token."""
    dest = session_token.get("dest")
    if not dest:
        raise ValueError("Session token missing 'dest' claim")
    shopify_domain = dest.removeprefix("https://")
    api_version = apps.get_app_config("shopify_app").SHOPIFY_API_VERSION
    access_token = (
        get_shop_model().objects.get(shopify_domain=shopify_domain).shopify_token
    )

    return Session.temp(shopify_domain, api_version, access_token)


def known_shop_required(func):
    """Require a valid shop query parameter and an existing shop record. Returns 401 if invalid."""
    def wrapper(*args, **kwargs):
        request = args[0]
        shop_domain = request.GET.get("shop", request.POST.get("shop"))
        try:
            check_shop_domain(request, kwargs, shop_domain)
            check_shop_known(request, kwargs)
        except (ValueError, get_shop_model().DoesNotExist):
            return HttpResponse(status=401, content="Invalid or unknown shop")
        return func(*args, **kwargs)

    return wrapper


def check_shop_domain(request, kwargs, shop_domain):
    """Sanitize and inject the shop domain into request and kwargs."""
    shop_domain = get_sanitized_shop_param(shop_domain)
    kwargs["shopify_domain"] = shop_domain
    request.shopify_domain = shop_domain
    return shop_domain


def get_sanitized_shop_param(shop_domain):
    """Validate and sanitize a shop domain. Raises ValueError if invalid."""
    sanitized_shop_domain = shop_url.sanitize_shop_domain(shop_domain)
    if not sanitized_shop_domain:
        raise ValueError("Shop must match 'example.myshopify.com'")
    return sanitized_shop_domain


def check_shop_known(request, kwargs):
    """Fetch the shop from DB and inject into request and kwargs. Raises DoesNotExist if not found."""
    shop = get_shop_model().objects.get(shopify_domain=kwargs.get("shopify_domain"))
    kwargs["shop"] = shop
    request.shop = shop


def latest_access_scopes_required(func):
    """Check if the shop's scopes match configured scopes. Sets scope_changes_required=True if they differ."""
    def wrapper(*args, **kwargs):
        shop = kwargs.get("shop")

        try:
            configured_access_scopes = apps.get_app_config(
                "shopify_app"
            ).SHOPIFY_API_SCOPES
            current_access_scopes = shop.access_scopes

            if ApiAccess(configured_access_scopes) != ApiAccess(current_access_scopes):
                kwargs["scope_changes_required"] = True
        except Exception:
            kwargs["scope_changes_required"] = True

        return func(*args, **kwargs)

    return wrapper


def get_app_proxy_shopify_domain(request):
    """Extract shop domain from an app proxy request (query param, header, or forwarded host)."""
    if request.GET.get("shop"):
        return request.GET["shop"]
    elif request.META.get("HTTP_X_SHOPIFY_SHOP_DOMAIN"):
        return request.META.get("HTTP_X_SHOPIFY_SHOP_DOMAIN")
    else:
        return request.META.get("HTTP_X_FORWARDED_HOST")


def app_proxy_view(func):
    """Validate HMAC for Shopify app proxy requests. Returns 400/401 on failure."""
    def wrapper(*args, **kwargs):
        request = args[0]

        shop_domain = get_app_proxy_shopify_domain(request)
        if shop_domain is None:
            return HttpResponse(status=400, content="Shop domain is required")

        shop = get_shop(shop_domain)
        if not shop.app_proxy_request_is_valid(request):
            return HttpResponse(status=401, content="Proxy request is invalid")

        kwargs["shop"] = shop

        return func(*args, **kwargs)

    return wrapper
