from rest_framework import status, viewsets
from rest_framework.views import APIView
from rest_framework.decorators import action
from rest_framework.response import Response
from .models import AppClients, IntegrationTokens
from .serializers import (
    AppRegistrationSerializer,
    RegisteredAppApproval,
    GetTokensSerializer,
    AppClientsCustomSerializer,
    RefreshTokenSerializer,
)
from rest_framework.permissions import IsAdminUser
from django.conf import settings
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.contrib.auth.hashers import check_password
from django.utils.crypto import get_random_string
from django.conf import settings
from django.utils import timezone
from datetime import timedelta
from drf_spectacular.utils import extend_schema, OpenApiResponse, OpenApiTypes


# Create your views here.
class RegisterApplicationAPIView(APIView):
    serializer_class = AppRegistrationSerializer
    permission_classes = []
    authentication_classes = []

    def post(self, request):
        data = request.data

        serializer = self.serializer_class(data=data)
        serializer.is_valid(raise_exception=True)
        validated_data = serializer.validated_data

        application_name = validated_data.get("application_name")
        contact_email = validated_data.get("contact_email")

        existing_app_name = AppClients.objects.filter(
            application_name__iexact=application_name
        )

        if existing_app_name.exists():
            return Response(
                {
                    "message": f"{application_name} is already registered. Try another app name."
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        existing_contact_email = AppClients.objects.filter(contact_email=contact_email)

        if existing_contact_email.exists():
            return Response(
                {
                    "message": f"{contact_email} is assigned to another app. Try another email."
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        AppClients.objects.create(**validated_data)

        return Response(
            {"message": "Application registered. Wait for approval."},
            status=status.HTTP_201_CREATED,
        )


class RegisteredApplicationApprovalAPIView(APIView):
    serializer_class = RegisteredAppApproval
    permission_classes = [IsAdminUser]

    def post(self, request):
        """
        status if <b>approved</b> or <b>rejected</b>
        """
        data = request.data

        serializer = self.serializer_class(data=data)
        serializer.is_valid(raise_exception=True)

        app_id = serializer.validated_data.get("id")
        approval_status: str = serializer.validated_data.get("status")

        if approval_status.lower() not in ["approved", "rejected"]:
            return Response(
                {
                    "message": f"{approval_status} is not valid. Choose 'approved' or 'rejected' only."
                },
                status=status.HTTP_400_BAD_REQUEST,
            )

        try:
            app_client = AppClients.objects.get(id=app_id)
            cuurent_approval_status = app_client.approval_status.lower()

            if cuurent_approval_status in ["approved", "rejected"]:
                return Response(
                    {
                        "message": f"The application was already {cuurent_approval_status}."
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            email_context = {
                "app_name": app_client.application_name,
            }

            if approval_status.lower() == "approved":
                first_chars_array = [
                    word[0].lower()
                    for word in app_client.application_name.split()
                    if word
                ]
                first_chars_str = "".join(first_chars_array)

                random_string_id = get_random_string(length=20)
                random_string_secret = get_random_string(length=20)

                client_id = f"cid_{first_chars_str}_{random_string_id}"
                client_secret = f"cs_{first_chars_str}_{random_string_secret}"

                app_client.approval_status = "approved"
                app_client.client_id = client_id
                app_client.client_secret = client_secret
                app_client.save()

                message = f"Application was {approval_status}. Check the contact email for the credentials."
                subject = "Application Approved"
                email_context["client_id"] = client_id
                email_context["client_secret"] = client_secret
                email_html_message = render_to_string(
                    "email/registered_app_approval.html", email_context
                )
            else:
                message = f"Application was {approval_status}. Register again after reviewing the submitted details."
                subject = "Application Rejected"
                email_html_message = render_to_string(
                    "email/registered_app_rejected.html", email_context
                )
                app_client.delete()  # Delete app after rejecting

            send_mail(
                subject=subject,
                message="",
                from_email=settings.EMAIL_HOST_USER,
                recipient_list=[app_client.contact_email],
                html_message=email_html_message,
                auth_user=settings.EMAIL_HOST_USER,
                auth_password=settings.EMAIL_HOST_PASSWORD,
            )

            return Response({"message": message}, status=status.HTTP_200_OK)
        except AppClients.DoesNotExist:
            return Response(
                {"message": "Application does not exists."},
                status=status.HTTP_404_NOT_FOUND,
            )


class GetTokensAPIView(APIView):
    serializer_class = GetTokensSerializer
    permission_classes = []
    authentication_classes = []

    @extend_schema(
        responses={
            200: OpenApiResponse(
                response={
                    "type": "object",
                    "properties": {
                        "access_token": {
                            "type": "string",
                            "example": "at.123qwerty456",
                        },
                        "refresh_token": {
                            "type": "string",
                            "example": "rt.123qwerty456",
                        },
                        "client": {
                            "type": "object",
                            "properties": {
                                "id": {"type": "string", "example": "uuid-123"},
                                "created_at": {
                                    "type": "string",
                                    "format": "date-time",
                                    "example": "2025-04-21T10:24:06.908207+08:00",
                                },
                                "application_name": {
                                    "type": "string",
                                    "example": "ABC App",
                                },
                                "application_type": {
                                    "type": "string",
                                    "example": "website",
                                },
                                "contact_email": {
                                    "type": "string",
                                    "example": "johndoe@gmail.com",
                                },
                                "contact_person_name": {
                                    "type": "string",
                                    "example": "John Doe",
                                },
                            },
                        },
                    },
                },
            )
        }
    )
    def post(self, request):
        """
        Expiration for Access and Refresh Tokens based on environment/stage:

        PRODUCTION:\n
        <b>30 minutes</b> for AT\n
        <b>1 day</b> for RT\n
        -----------------------------
        DEVELOPMENT/TESTING:\n
        <b>7 days</b> for AT\n
        <b>14 days</b> for RT
        """
        data = request.data

        serializer = self.serializer_class(data=data)
        serializer.is_valid(raise_exception=True)
        validated_data = serializer.validated_data

        client_id = validated_data.get("client_id")
        client_secret = validated_data.get("client_secret")

        try:
            app_client = AppClients.objects.get(client_id=client_id)
            is_approved = app_client.approval_status.lower() == "approved"
            hashed_client_secret = app_client.client_secret

            is_client_secret_correct = check_password(
                password=client_secret, encoded=hashed_client_secret
            )

            if not is_client_secret_correct:
                return Response(
                    {
                        "message": "App with the client id exists but wrong client secret."
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            if not is_approved:
                return Response(
                    {"error": "App is not yet approved."},
                    status=status.HTTP_403_FORBIDDEN,
                )

            serialized_app_client = AppClientsCustomSerializer(
                app_client, many=False
            ).data

            first_chars_array = [
                word[0].lower() for word in app_client.application_name.split() if word
            ]
            first_chars_str = "".join(first_chars_array)

            access_token = f"{first_chars_str}.{get_random_string(length=45)}"
            refresh_token = f"{first_chars_str}.{get_random_string(length=55)}"

            client_tokens = IntegrationTokens.objects.filter(client=app_client)

            environment = settings.ENVIRONMENT

            if environment.lower() in ["prod", "production"]:
                at_expires_at = timezone.localtime() + timedelta(minutes=30)
                rt_expires_at = timezone.localtime() + timedelta(days=1)
            else:
                at_expires_at = timezone.localtime() + timedelta(days=7)
                rt_expires_at = timezone.localtime() + timedelta(days=14)

            if not client_tokens.exists():
                IntegrationTokens.objects.create(
                    client=app_client,
                    access_token=access_token,
                    refresh_token=refresh_token,
                    access_token_expires_at=at_expires_at,
                    refresh_token_expires_at=rt_expires_at,
                )
            else:
                tokens = client_tokens.first()
                tokens.access_token = access_token
                tokens.refresh_token = refresh_token
                tokens.access_token_expires_at = at_expires_at
                tokens.refresh_token_expires_at = rt_expires_at
                tokens.save()

            return Response(
                {
                    "access_token": access_token,
                    "refresh_token": refresh_token,
                    "client": serialized_app_client,
                },
                status=status.HTTP_200_OK,
            )
        except AppClients.DoesNotExist:
            return Response(
                {"message": "No registered app is associated with the client id."},
                status=status.HTTP_404_NOT_FOUND,
            )


class RefreshTokenAPIView(APIView):
    serializer_class = RefreshTokenSerializer
    permission_classes = []
    authentication_classes = []

    @extend_schema(
        responses={
            200: OpenApiResponse(
                response={
                    "type": "object",
                    "properties": {
                        "access_token": {
                            "type": "string",
                            "example": "at.123qwerty456",
                        },
                        "refresh_token": {
                            "type": "string",
                            "example": "rt.123qwerty456",
                        },
                        "client": {
                            "type": "object",
                            "properties": {
                                "id": {"type": "string", "example": "uuid-123"},
                                "created_at": {
                                    "type": "string",
                                    "format": "date-time",
                                    "example": "2025-04-21T10:24:06.908207+08:00",
                                },
                                "application_name": {
                                    "type": "string",
                                    "example": "ABC App",
                                },
                                "application_type": {
                                    "type": "string",
                                    "example": "website",
                                },
                                "contact_email": {
                                    "type": "string",
                                    "example": "johndoe@gmail.com",
                                },
                                "contact_person_name": {
                                    "type": "string",
                                    "example": "John Doe",
                                },
                            },
                        },
                    },
                },
            )
        }
    )
    def post(self, request):
        data = request.data

        serializer = self.serializer_class(data=data)
        serializer.is_valid(raise_exception=True)
        client_id = serializer.validated_data.get("client_id")
        refresh_token = serializer.validated_data.get("refresh_token")

        try:
            client_tokens = IntegrationTokens.objects.get(
                client__client_id=client_id, refresh_token=refresh_token
            )

            is_refresh_token_expired = client_tokens.is_refresh_token_expired

            if is_refresh_token_expired:
                return Response(
                    {
                        "message": "Refresh Token is already expired. Generate new tokens."
                    },
                    status=status.HTTP_400_BAD_REQUEST,
                )

            first_chars_array = [
                word[0].lower()
                for word in client_tokens.client.application_name.split()
                if word
            ]
            first_chars_str = "".join(first_chars_array)

            access_token = f"{first_chars_str}.{get_random_string(length=45)}"
            at_expires_at = timezone.localtime() + timedelta(minutes=30)

            serialized_app_client = AppClientsCustomSerializer(
                client_tokens.client, many=False
            ).data

            client_tokens.access_token = access_token
            client_tokens.access_token_expires_at = at_expires_at
            client_tokens.save()

            return Response(
                {
                    "access_token": access_token,
                    "refresh_token": refresh_token,
                    "client": serialized_app_client,
                },
                status=status.HTTP_200_OK,
            )
        except IntegrationTokens.DoesNotExist:
            return Response(
                {
                    "message": f"Refresh Token not associated with client id '{client_id}'."
                },
                status=status.HTTP_404_NOT_FOUND,
            )
