import logging
import mimetypes
import os
import traceback

import requests
from django.contrib.sitemaps.views import index as _sitemap_index_view
from django.contrib.sitemaps.views import sitemap as _sitemap_detail_view
from django.core.cache import cache
from django.core.exceptions import BadRequest
from django.http import FileResponse, Http404, JsonResponse
from django.shortcuts import redirect
from django.utils.http import urlsafe_base64_decode
from django.utils.translation import gettext_lazy as _
from django.views.decorators.cache import cache_page
from django.views.decorators.vary import vary_on_headers
from django_ratelimit.decorators import ratelimit
from djangorestframework_camel_case.render import CamelCaseJSONRenderer
from djangorestframework_camel_case.util import camelize
from drf_spectacular.utils import extend_schema_view
from drf_spectacular.views import SpectacularRedocView, SpectacularSwaggerView
from graphene_file_upload.django import FileUploadGraphQLView
from rest_framework import status
from rest_framework.permissions import AllowAny
from rest_framework.renderers import MultiPartRenderer
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_xml.renderers import XMLRenderer
from rest_framework_yaml.renderers import YAMLRenderer
from sentry_sdk import capture_exception

from core.docs.drf.views import (
    BUY_AS_BUSINESS_SCHEMA,
    CACHE_SCHEMA,
    CONTACT_US_SCHEMA,
    LANGUAGE_SCHEMA,
    PARAMETERS_SCHEMA,
    REQUEST_CURSED_URL_SCHEMA,
    SEARCH_SCHEMA,
)
from core.elasticsearch import process_query
from core.models import DigitalAssetDownload, Order
from core.serializers import (
    BuyAsBusinessOrderSerializer,
    CacheOperatorSerializer,
    ContactUsSerializer,
    LanguageSerializer,
)
from core.utils import get_project_parameters, is_url_safe
from core.utils.caching import web_cache
from core.utils.emailing import contact_us_email
from core.utils.languages import get_flag_by_language
from evibes import settings
from evibes.settings import LANGUAGES
from payments.serializers import TransactionProcessSerializer

logger = logging.getLogger("django")


@cache_page(60 * 60 * 12)
@vary_on_headers("Host")
def sitemap_index(request, *args, **kwargs):
    """
    Handles the request for the sitemap index and returns an XML response. It ensures the response includes
    the appropriate content type header for XML.

    Args:
        request: The HTTP request object.
        *args: Additional positional arguments passed to the view.
        **kwargs: Additional keyword arguments passed to the view.

    Returns:
        A response object containing the sitemap index in XML format, with the proper content type set as
        "application/xml; charset=utf-8".
    """
    response = _sitemap_index_view(request, *args, **kwargs)
    response["Content-Type"] = "application/xml; charset=utf-8"
    return response


@cache_page(60 * 60 * 24)
@vary_on_headers("Host")
def sitemap_detail(request, *args, **kwargs):
    """
    Handles the detailed view response for a sitemap. This function processes
    the request, fetches the appropriate sitemap detail response, and sets the
    Content-Type header for XML responses.

    Args:
        request: An HTTP request object containing request metadata, such as
            headers and HTTP method.
        *args: Additional positional arguments provided dynamically to the
            underlying sitemap detail view function.
        **kwargs: Additional keyword arguments provided dynamically to the
            underlying sitemap detail view function.

    Returns:
        HttpResponse: A response object with content representing the requested
        sitemap details. The Content-Type header is explicitly set to
        "application/xml; charset=utf-8".
    """
    response = _sitemap_detail_view(request, *args, **kwargs)
    response["Content-Type"] = "application/xml; charset=utf-8"
    return response


class CustomGraphQLView(FileUploadGraphQLView):
    """
    A custom GraphQL view class that extends the functionality of FileUploadGraphQLView.

    This class serves as a customization extension of FileUploadGraphQLView that allows modification
    or enhancement of specific behaviors, particularly the context handling for GraphQL requests.

    """

    def get_context(self, request):
        return request


class CustomSwaggerView(SpectacularSwaggerView):
    """
    CustomSwaggerView is a subclass of SpectacularSwaggerView.

    This class overrides the `get_context_data` method to
    add extra context to the response. It modifies the context by
    including the absolute URI of the current request as the `script_url`.
    This can be useful in scenarios where the script or reference
    URL needs to be dynamically generated and included in the context.
    """

    def get_context_data(self, **kwargs):
        # noinspection PyUnresolvedReferences
        context = super().get_context_data(**kwargs)
        context["script_url"] = self.request.build_absolute_uri()
        return context


class CustomRedocView(SpectacularRedocView):
    """
    CustomRedocView provides a customized version of the SpectacularRedocView.

    This class extends the SpectacularRedocView to include additional
    functionality, such as dynamically setting the `script_url` in the
    context data. It is designed to be used where customized behavior
    for rendering ReDoc UI is required, specifically adapting the script
    URL for the current request environment.
    """

    def get_context_data(self, **kwargs):
        # noinspection PyUnresolvedReferences
        context = super().get_context_data(**kwargs)
        context["script_url"] = self.request.build_absolute_uri()
        return context


@extend_schema_view(**LANGUAGE_SCHEMA)
class SupportedLanguagesView(APIView):
    """
    Handles retrieving the list of supported languages.

    This class provides an endpoint to return information about available languages.
    It is configured with relevant serializers, permission classes, and renderers
    for flexibility in response formats and access permissions. The endpoint
    supports retrieving the list of languages with their respective codes, names,
    and flags.

    Attributes:
        serializer_class (Serializer): Serializer used for formatting the response data.
        permission_classes (list): Permissions applied to restrict the endpoint access.
        renderer_classes (list): Renderers available for formatting response output.

    Methods:
        get(self, request): Retrieves the list of supported languages.

    """

    serializer_class = LanguageSerializer
    permission_classes = [
        AllowAny,
    ]
    renderer_classes = [
        CamelCaseJSONRenderer,
        MultiPartRenderer,
        XMLRenderer,
        YAMLRenderer,
    ]

    def get(self, request):
        return Response(
            data=self.serializer_class(
                [
                    {
                        "code": lang[0],
                        "name": lang[1],
                        "flag": get_flag_by_language(lang[0]),
                    }
                    for lang in LANGUAGES
                ],
                many=True,
            ).data,
            status=status.HTTP_200_OK,
        )


@extend_schema_view(**PARAMETERS_SCHEMA)
class WebsiteParametersView(APIView):
    """
    Handles operations related to website parameters.

    This class is a Django Rest Framework view that allows clients to retrieve
    the parameters of a website. It uses different renderers to present the data
    in various formats. The view is publicly accessible.

    Attributes
    ----------
    serializer_class
        A placeholder for a DRF serializer, it is set to None since no serializer
        is explicitly used in this view.
    permission_classes
        A list indicating the permissions required to access this view. In this case,
        `AllowAny`, meaning the view is open to everyone.
    renderer_classes
        A list of renderers available for this view, supporting CamelCase JSON,
        multipart forms, XML, and YAML formats.

    Methods
    -------
    get(request)
        Handles HTTP GET requests to fetch website parameters.
    """

    serializer_class = None
    permission_classes = [
        AllowAny,
    ]
    renderer_classes = [
        CamelCaseJSONRenderer,
        MultiPartRenderer,
        XMLRenderer,
        YAMLRenderer,
    ]

    def get(self, request):
        return Response(data=camelize(get_project_parameters()), status=status.HTTP_200_OK)


@extend_schema_view(**CACHE_SCHEMA)
class CacheOperatorView(APIView):
    """
    View for managing cache operations.

    This class provides an API view for handling cache operations such as setting cache
    data with a specified key and timeout. It leverages multiple renderer classes for
    serializing outputs in various formats.

    Attributes:
        serializer_class (type): Serializer to validate and deserialize input data.
        permission_classes (list): List of permission classes to apply access
            restrictions.
        renderer_classes (list): List of renderer classes to serialize the output
            in desired formats.

    Methods:
        post(request, *args, **kwargs): Handles HTTP POST requests to set cache
            data based on the provided key and timeout.
    """

    serializer_class = CacheOperatorSerializer
    permission_classes = [
        AllowAny,
    ]
    renderer_classes = [
        CamelCaseJSONRenderer,
        MultiPartRenderer,
        XMLRenderer,
        YAMLRenderer,
    ]

    def post(self, request, *args, **kwargs):
        return Response(
            data=web_cache(
                request,
                request.data.get("key"),
                request.data.get("data"),
                request.data.get("timeout"),
            ),
            status=status.HTTP_200_OK,
        )


@extend_schema_view(**CONTACT_US_SCHEMA)
class ContactUsView(APIView):
    """
    Handles contact us form submissions via a REST API.

    This view processes user submissions for a "Contact Us" form. It validates the received
    data using a serializer, applies rate limiting for IP-based requests, and sends emails
    asynchronously. The view is prepared to handle multiple response formats using configured
    renderers.

    Attributes:
        serializer_class: The serializer class used to validate incoming data.
        renderer_classes: A list of renderers to support multiple response formats.

    Methods:
        post: Handles POST requests to process form submissions.

    """

    serializer_class = ContactUsSerializer
    renderer_classes = [
        CamelCaseJSONRenderer,
        MultiPartRenderer,
        XMLRenderer,
        YAMLRenderer,
    ]

    @ratelimit(key="ip", rate="2/h")
    def post(self, request, *args, **kwargs):
        serializer = self.serializer_class(data=request.data)
        serializer.is_valid(raise_exception=True)
        contact_us_email.delay(serializer.validated_data)

        return Response(data=serializer.data, status=status.HTTP_200_OK)


@extend_schema_view(**REQUEST_CURSED_URL_SCHEMA)
class RequestCursedURLView(APIView):
    """
    Handles requests for processing and validating URLs from incoming POST requests.

    Particularly intended for validating and processing URLs provided by clients. It uses rate-limiting, caching, and
    various response format renderers to optimize performance and ensure safe handling of external data.

    Attributes:
        permission_classes (list): Specifies the permissions required to access this view.
        renderer_classes (list): Configures the response format renderers available for this view.

    Methods:
        post: Handles the POST request to validate the URL, fetches its data if valid,
         and returns the processed response.
    """

    permission_classes = [
        AllowAny,
    ]
    renderer_classes = [
        CamelCaseJSONRenderer,
        MultiPartRenderer,
        XMLRenderer,
        YAMLRenderer,
    ]

    @ratelimit(key="ip", rate="10/h")
    def post(self, request, *args, **kwargs):
        url = request.data.get("url")
        if not is_url_safe(url):
            return Response(
                data={"error": _("only URLs starting with http(s):// are allowed")},
                status=status.HTTP_400_BAD_REQUEST,
            )
        try:
            data = cache.get(url, None)
            if not data:
                response = requests.get(url, headers={"content-type": "application/json"})
                response.raise_for_status()
                data = camelize(response.json())
                cache.set(url, data, 86400)
            return Response(
                data=data,
                status=status.HTTP_200_OK,
            )
        except Exception as e:
            return Response(
                data={"error": str(e)},
                status=status.HTTP_400_BAD_REQUEST,
            )


@extend_schema_view(**SEARCH_SCHEMA)
class GlobalSearchView(APIView):
    """
    Class-based view for handling global search functionality.

    This class is designed to process search queries from HTTP GET requests. It is
    capable of rendering results in multiple formats including CamelCase JSON,
    MultiPart, XML, and YAML. The class uses a custom schema for API documentation
    and processes search queries passed as parameters in the request.

    Attributes:
        renderer_classes (list): List of renderer classes used to serialize responses
            into various formats such as CamelCase JSON, MultiPart, XML, and YAML.

    Methods:
        get: Handles HTTP GET requests by processing the search query and returning
            formatted search results.

    """

    renderer_classes = [
        CamelCaseJSONRenderer,
        MultiPartRenderer,
        XMLRenderer,
        YAMLRenderer,
    ]

    def get(self, request, *args, **kwargs):
        return Response(camelize({"results": process_query(query=request.GET.get("q", "").strip(), request=request)}))


@extend_schema_view(**BUY_AS_BUSINESS_SCHEMA)
class BuyAsBusinessView(APIView):
    """
    View for buying as a business.

    This view handles the logic of creating orders and processing transactions when a
    business makes a purchase. It ensures that the request data is properly validated
    and processed, handles the creation of the order, and starts the transaction process.
    The view also restricts the rate of requests based on the client's IP address
    to prevent abuse.

    Attributes:
        schema (class): Extended schema for API documentation.

    Methods:
        post(request, *_args, **kwargs):
            Handles the "POST" request to process a business purchase.
    """

    @ratelimit(key="ip", rate="2/h", block=True)
    def post(self, request, *_args, **kwargs):
        serializer = BuyAsBusinessOrderSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        order = Order.objects.create(status="MOMENTAL")
        products = [product.get("product_uuid") for product in serializer.validated_data.get("products")]
        transaction = order.buy_without_registration(
            products=products,
            promocode_uuid=serializer.validated_data.get("promocode_uuid"),
            customer_name=serializer.validated_data.get("customer_name"),
            customer_email=serializer.validated_data.get("customer_email"),
            customer_phone=serializer.validated_data.get("customer_phone"),
            customer_billing_address=serializer.validated_data.get("customer_billing_address_uuid"),
            customer_shipping_address=serializer.validated_data.get("customer_shipping_address_uuid"),
            payment_method=serializer.validated_data.get("payment_method"),
            is_business=True,
        )
        return Response(
            status=status.HTTP_202_ACCEPTED,
            data=TransactionProcessSerializer(transaction).data,
        )


def download_digital_asset_view(request, *args, **kwargs):
    """
    Handles the downloading of a digital asset associated with an order. Ensures that users
    are permitted to download the asset only once. Validates the request, retrieves the file,
    and serves it as a downloadable response. Returns appropriate error responses for different
    failure scenarios.

    Args:
        request: The HTTP request object containing information about the client request.
        *args: Additional positional arguments.
        **kwargs: Additional keyword arguments.

    Raises:
        BadRequest: If the digital asset has already been downloaded.
        DigitalAssetDownload.DoesNotExist: If the requested digital asset cannot be found.

    Returns:
        A FileResponse containing the digital asset file if the request is valid. Returns
        a JsonResponse with an error message if an error occurs during the process.
    """
    try:
        logger.debug(f"download_digital_asset_view: {kwargs}")
        uuid = urlsafe_base64_decode(str(kwargs.get("encoded_uuid"))).decode("utf-8")

        download = DigitalAssetDownload.objects.get(order_product__uuid=uuid)

        if download.num_downloads >= 1:
            raise BadRequest(_("you can only download the digital asset once"))

        download.num_downloads += 1
        download.save()

        file_path = download.order_product.product.stocks.first().digital_asset.file.path

        content_type, encoding = mimetypes.guess_type(file_path)
        if not content_type:
            content_type = "application/octet-stream"

        with open(file_path, "rb") as file:
            response = FileResponse(file, content_type=content_type)
            filename = os.path.basename(file_path)
            response["Content-Disposition"] = f'attachment; filename="{filename}"'
            return response

    except BadRequest as e:
        return JsonResponse(camelize({"error": str(e)}), status=400)

    except DigitalAssetDownload.DoesNotExist:
        return JsonResponse(camelize({"error": "Digital asset not found"}), status=404)

    except Exception as e:
        capture_exception(e)
        return JsonResponse(
            camelize(
                {
                    "error": "An error occurred while trying to download the digital asset",
                    "traceback": traceback.format_exc() if settings.DEBUG else None,
                    "received": {"encoded_uuid": kwargs.get("encoded_uuid", "")},
                }
            ),
            status=500,
        )


def favicon_view(request, *args, **kwargs):
    """
    Handles requests for the favicon of a website. This function attempts to serve the favicon
    file located in the static directory of the project. If the favicon file is not found,
    an HTTP 404 error is raised to indicate the resource is unavailable.

    Args:
        request: The HTTP request object.
        *args: Additional positional arguments that are ignored in this function.
        **kwargs: Additional keyword arguments that are ignored in this function.

    Returns:
        FileResponse: A file response containing the favicon image with the content-type
        "image/x-icon".

    Raises:
        Http404: Raised if the favicon file is not found.
    """
    try:
        favicon_path = os.path.join(settings.BASE_DIR, "static/favicon.png")
        return FileResponse(open(favicon_path, "rb"), content_type="image/x-icon")
    except FileNotFoundError as fnfe:
        raise Http404(_("favicon not found")) from fnfe


def index(request, *args, **kwargs):
    """
    Redirects the request to the admin index page.

    The function handles incoming HTTP requests and redirects them to the Django
    admin interface index page. It uses Django's `redirect` function for handling
    the HTTP redirection.

    Args:
        request: The HttpRequest object representing the incoming request.
        *args: Additional positional arguments, if any.
        **kwargs: Additional keyword arguments, if any.

    Returns:
        HttpResponseRedirect: An HTTP response that redirects to the admin index.
    """
    return redirect("admin:index")
