from datetime import datetime
from flask import url_for, render_template
from marshmallow import ValidationError, EXCLUDE  # Import ValidationError from Marshmallow.
from flask_restx import Namespace, Resource, fields, Model
from flask_jwt_extended import create_access_token, create_refresh_token, jwt_required, get_jwt_identity, get_jwt, decode_token


from app.utils.password_generator import example_password

from app.schema.login import LoginSchema
from app.schema.email import EmailSchema
from app.schema.register import RegisterSchema
from app.schema.token import RefreshTokenSchema

from app.services.auth import AuthService
from app.services.token import TokenService
from app.services.mail import MailService


auth_namespace = Namespace('auth', description='User Authentication')

# Define the LoginInput model  
LoginInput = Model('LoginInput', {
    'email': fields.String(
        description='Email Or Username for authentication',
        required=True,
        min_length=5,  # Minimum length for the email
        max_length=225,  # Maximum length for the email
        example='admin@pige.com'  # Example email address
    ), 

    'password': fields.String(
        description='Password for authentication',
        required=True,
        min_length=6,  # Minimum length for the password
        max_length=225,  # Maximum length for the password
        example=example_password 
    )
})
# Define the EmailInput model
EmailInput = Model('EmailInput', {
    'email': fields.String(required=True, description='The user\'s email address'),
})
# Define the RegistrationInput model
RegistrationInput = Model('RegistrationInput', {
    'company_name': fields.String(
        description='User company name for registration',
        required=False,
        example='Company Pige'
    ),
    'company_type': fields.String(
        description='User Company type for registration',
        required=True,
        enum=RegisterSchema.allowed_company_types,
        example='Anonymous Company'
    ),
    'username': fields.String(
        description='Username for registration',
        required=False,
        example='admin@pige'
    ),
    'email': fields.String(
        description='Email for registration',
        required=True,
        example='admin@pige.com'
    ),
    'password': fields.String(
        description='Password for registration',
        required=True,
        example=example_password
    ),
    'password_confirm': fields.String(
        description='Password Confirmation for registration',
        required=True,
        example=example_password
    )
})
# Define the RefreshTokenInput model
RefreshTokenInput = Model('RefreshTokenInput', {
    'refresh': fields.String(
        description='Refresh token for obtaining a new access token',
        required=True
    )
})


auth_namespace.models[LoginInput.name] = LoginInput
auth_namespace.models[EmailInput.name] = EmailInput
auth_namespace.models[RegistrationInput.name] = RegistrationInput
auth_namespace.models[RefreshTokenInput.name] = RefreshTokenInput


# Define a route for the login operation.
@auth_namespace.route("/login") # Define a route for the 'Login' endpoint
class LoginResource(Resource): # Create a class for the 'Login' resource 
    @auth_namespace.doc('User login endpoint', security=None) 
    @auth_namespace.expect(LoginInput)
    def post(self):
        """
        User Login Endpoint: Authenticate and obtain access & refresh tokens.

        This endpoint allows users to log in by providing their email and password. Upon successful
        authentication, it returns access and refresh tokens that can be used for authorized access
        to protected resources.

        :return: A JSON response containing access and refresh tokens if authentication is successful,
                or an error message if authentication fails.
        """

        try:
            # Get the request JSON data from the payload.
            data = auth_namespace.payload   

            # Create an instance of the Marshmallow schema for input validation.
            login_schema = LoginSchema() 
            
            # Validate the input data using the schema instance.
            try:
                validated_data = login_schema.load(data, unknown=EXCLUDE)
            except ValidationError as err:
                # Handle validation errors by returning a 400 (Bad Request) response with error msgs.
                return {
                    'msg': err.messages
                }, 400 # Bad Request
            
            # Check if the provided email valid
            if not AuthService.get_user_by_email(validated_data["email"]):
                return {
                        "msg": "Access denied: User with this email does not exist."
                }, 401 # Unauthorized
            
            user = AuthService.authenticate(validated_data['email'], validated_data['password'])

            # Check if the provided login credentials are valid
            if not user: 
                return {
                    "msg": "Access denied: Incorrect password."
                }, 401 # Unauthorized

            # Check if the user is active
            if not user.is_active:
                return {
                    'msg': 'Access denied: Account disabled, contact admin.',
                }, 403  # Forbidden
            
            # Check if the user is blocked
            if user.is_blocked:
                return {
                    'msg': 'Access denied: Account Forbidden.',
                }, 403  # Forbidden

            if user.is_verified == False:
                pass  
            
            # Update the last_login & last_seen timestamp for the user
            user.update_last_login()
            user.update_last_seen()
            user.save()
            
            # 
            additional_claims = {
                "is_superuser": user.is_superuser,
                "is_active": user.is_active,
                "is_staff": user.is_staff,
                "is_admin": user.is_admin, 
            }

            # You can add user data to the response here.
            return {
                'msg': 'Login successful',
                'user_id': user.public_id,  # Include the user's email in the response
                'token': {
                    'access': create_access_token(identity=user.email, additional_claims=additional_claims),  # Generate an access token
                    'refresh': create_refresh_token(identity=user.email)  # Generate a refresh token
                }  
            }, 200 # OK

        except Exception as e: 
            if hasattr(e, 'code') and e.code is not None:
                # If the exception has a status code attribute, return it
                return {
                    'msg': str(e),
                }, e.code
            else:
                # If no specific status code is provided, return a 500 Internal Server Error
                return {
                    'msg': 'An error occurred while processing your request.',
                    'error': str(e)
                }, 500  # Internal Server Error

# Define a route for the register operation.
@auth_namespace.route("/register") # Define a route for the 'Register' endpoint
class RegisterResource(Resource): # Create a class for the 'Register' resource
    @auth_namespace.doc('User registration endpoint', security=None)
    @auth_namespace.expect(RegistrationInput)
    def post(self):
        """
        User Registration Endpoint: Create a new user account.

        This endpoint allows users to create a new account by providing their registration details,
        including username, first name, last name, email, and password. Upon successful registration,
        a user account is created, and they can use it to log in.

        :return: A JSON response indicating successful registration or an error message if registration fails.
        """
        try:
            # Get the request JSON data from the payload.
            data = auth_namespace.payload   

            # Create an instance of the Marshmallow schema for input validation.
            register_schema = RegisterSchema() 
        
            # Validate the input data using the schema instance.
            try:
                validated_data = register_schema.load(data)
            except ValidationError as err:
                # Handle validation errors by returning a 400 (Bad Request) response with error msgs.
                return {
                    'msg': err.messages
                }, 400 # Bad Request
            
            # Remove the 'password_confirm' field from the validated data
            if 'password_confirm' in validated_data:
                del validated_data['password_confirm']
 
            # Check if the provided email valid 
            if AuthService.get_user_by_email(validated_data["email"]) is not None:
                # User with this email exists, handle the registration logic here
                return {
                    "msg": "User with this email already exists. Please use a different email."
                }, 400  # Bad Request

            # Create a new user with the provided data
            user = AuthService.create_user(validated_data)

            # Check if user registration was successful
            if user is None:
                return {
                    'msg': 'User registration failed',
                }, 500 # Internal Server Error
             
            # Generate an activation token
            activation_token = TokenService.generate_token(user.email, "account_activation") 
            # Generate the activation link dynamically
            activation_link = url_for('auth_verify_email_resource', activation_token=activation_token, _external=True)
            # 
            MailService.send_email( 
                subject="Account Activation: Email Verification", 
                recipient = user.email, 
                message = render_template('activation_email.html', activation_link=activation_link),
                html=True
            )
 
            return {
                'msg': 'User registered successfully',
            }, 201 # Created
        
        except Exception as e:
            if hasattr(e, 'code') and e.code is not None:
                # If the exception has a status code attribute, return it
                return {
                    'msg': str(e),
                }, e.code
            else:
                # If no specific status code is provided, return a 500 Internal Server Error
                return {
                    'msg': 'An error occurred while processing your request.',
                    'error': str(e)
                }, 500  # Internal Serve
            
# Define a route for logout operation.
@auth_namespace.route('/logout') # Define a route for the 'Logout' endpoint
class LogoutResource(Resource): # Create a class for the 'Logout' resource
    @jwt_required()  # Ensure the request is protected by a valid JWT access token
    @auth_namespace.expect(RefreshTokenInput)  # Specify the expected input model
    @auth_namespace.doc(security='jwt')  # Document the security requirement
    def post(self):
        """
        User Logout Endpoint (POST): Revoke access and refresh tokens.

        This endpoint allows users to log out, revoking their access and refresh tokens.
        It checks the validity and expiration status of the refresh token before proceeding.

        :return: A response message indicating success or an error message.
        """ 
        try:
            # Get the request JSON data from the payload.
            data = auth_namespace.payload   

            # Create an instance of the Marshmallow schema for input validation.
            refresh_token_schema = RefreshTokenSchema() 
            
            # Validate the input data using the schema instance.
            try:
                validated_data = refresh_token_schema.load(data)
            except ValidationError as err:
                # Handle validation errors by returning a 400 (Bad Request) response with error messages.
                return {
                    'msg': err.messages
                }, 400  # Bad Request
            
            # Get the JTI (JWT ID) from both tokens
            current_user_id = AuthService.get_user_by_email( get_jwt_identity()).id
            # Check the expiration time of the refresh token
            refresh_exp = decode_token(validated_data['refresh']).get('exp') 
                
            if refresh_exp is not None and datetime.utcnow().timestamp() >= refresh_exp:
                return {
                    "msg": "Refresh token has expired."
                }, 401  # Unauthorized
            
            # Check if the access and refresh tokens are already blacklisted for the current user
            if TokenService.is_blacklisted(current_user_id, decode_token(validated_data['refresh'])['jti']):
                return {
                    "msg": "Access denied: Bad Token."
                }, 400  # Bad Request 
            
            TokenService.blacklist_token(validated_data['refresh'], 'refresh', current_user_id, decode_token(validated_data['refresh'])['jti'], "User Logout")
            TokenService.blacklist_token(None, 'access', current_user_id, get_jwt()['jti'], "User Logout")
 
            return {
                    'msg': 'User logged out successfully'
                }, 200
    
        except Exception as e:
            if hasattr(e, 'code') and e.code is not None:
                # If the exception has a status code attribute, return it
                return {
                    'msg': str(e),
                }, e.code
            else:
                # If no specific status code is provided, return a 500 Internal Server Error
                return {
                    'msg': 'An error occurred while processing your request.',
                    'error': str(e)
                }, 500  # Internal Server Error

# Define a route for token verification operation.
@auth_namespace.route("/token/verify") # Define a route for the 'VerifyToken' endpoint
class VerifyTokenResource(Resource): # Create a class for the 'VerifyToken' resource
    @jwt_required()  # Ensure the request is protected by a valid JWT access token using the 'jwt_required' decorator
    @auth_namespace.doc(security='jwt')  # Document the security requirement (JWT) for this endpoint
    def get(self):
        """
        Token Verification Endpoint: Verify the validity of an access token.

        This endpoint allows users to verify the validity of their access token.
        It checks if the token is valid and if the user associated with the token exists.

        :return: A response message indicating success or an error message.
        """ 
        try:  # Use a try-except block to handle potential exceptions
            # Check if there's a valid access token in the request
            current_user_id = get_jwt_identity()  # Retrieve the user's identity from the JWT access token
            
            if current_user_id is None:
                # If there's no valid identity, return an unauthorized response
                return {
                    "msg": "Access denied: Invalid or expired token."
                }, 401  # Unauthorized
              
            # Check if the user exists (you should implement your own logic for this) 
            if AuthService.get_user_by_email(current_user_id) is None : 
                # If the user does not exist, return an unauthorized response
                return {
                    "msg": "Access denied: Bad Token."
                }, 401  # Unauthorized

            return {
                'msg': 'Token verified successfully.'
            }, 200  # OK response indicating successful token verification
        except Exception as e:
            if hasattr(e, 'code') and e.code is not None:
                # If the exception has a status code attribute, return it
                return {
                    'msg': str(e),
                }, e.code
            else:
                # If no specific status code is provided, return a 500 Internal Server Error
                return {
                    'msg': 'An error occurred while processing your request.',
                    'error': str(e)
                }, 500  # Internal Server Error

    @jwt_required()  # Ensure the request is protected by a valid JWT access token using the 'jwt_required' decorator
    @auth_namespace.doc(security='jwt')  # Document the security requirement (JWT) for this endpoint
    def post(self):
        """
        Token Verification Endpoint: Verify the validity of an access token.

        This endpoint allows users to verify the validity of their access token.
        It checks if the token is valid and if the user associated with the token exists.

        :return: A response message indicating success or an error message.
        """ 
        try:  # Use a try-except block to handle potential exceptions
            # Check if there's a valid access token in the request
            current_user_id = get_jwt_identity()  # Retrieve the user's identity from the JWT access token
            
            if current_user_id is None:
                # If there's no valid identity, return an unauthorized response
                return {
                    "msg": "Access denied: Invalid or expired token."
                }, 401  # Unauthorized
              
            # Check if the user exists (you should implement your own logic for this) 
            if AuthService.get_user_by_email(current_user_id) is None : 
                # If the user does not exist, return an unauthorized response
                return {
                    "msg": "Access denied: Bad Token."
                }, 401  # Unauthorized

            return {
                'msg': 'Token verified successfully.'
            }, 200  # OK response indicating successful token verification
        except Exception as e:
            if hasattr(e, 'code') and e.code is not None:
                # If the exception has a status code attribute, return it
                return {
                    'msg': str(e),
                }, e.code
            else:
                # If no specific status code is provided, return a 500 Internal Server Error
                return {
                    'msg': 'An error occurred while processing your request.',
                    'error': str(e)
                }, 500  # Internal Server Error

# Define a route for refreshing tokens operation.
@auth_namespace.route('/token/refresh') # Define a route for the 'RefreshToken' endpoint
class RefreshTokenResource(Resource):  # Create a class for the 'RefreshToken' resource
    @jwt_required()  # Ensure the request is protected by a valid JWT access token using the 'jwt_required' decorator
    @auth_namespace.doc(security='jwt')  # Document the security requirement (JWT) for this endpoint
    def post(self):
        """
        Token Refresh Endpoint: Refresh an access token using a valid refresh token.

        This endpoint allows users to refresh their access token using a valid refresh token.
        It validates the refresh token and issues a new access token.

        :return: A response message indicating success or an error message.
        """  
        try:  # Use a try-except block to handle potential exceptions
            # Check if there's a valid access token in the request
            current_user_id = get_jwt_identity()  # Retrieve the user's identity from the JWT access token
            
            if current_user_id is None:
                # If there's no valid identity, return an unauthorized response
                return {
                    "msg": "Access denied: Invalid or expired token."
                }, 401 # Unauthorized

            # Get the request JSON data from the payload.
            data = auth_namespace.payload   

            # Create an instance of the Marshmallow schema for input validation.
            refresh_token_schema = RefreshTokenSchema() 

            # Validate the input data using the schema instance.
            try:
                validated_data = refresh_token_schema.load(data)
            except ValidationError as err:
                # Handle validation errors by returning a 400 (Bad Request) response with error messages.
                return {
                    'msg': err.messages
                }, 400  # Bad Request

            # Verify the provided refresh token
            refresh_decode = decode_token(validated_data["refresh"]) 

            # Check the expiration time of the refresh token
            refresh_exp = refresh_decode.get('exp') 
            
            if refresh_exp is not None and datetime.utcnow().timestamp() >= refresh_exp:
                return {
                    "msg": "Access denied: Refresh token has expired."
                }, 401  # Unauthorized
            
            # Check if the current user ID matches the one in the refresh token
            if current_user_id != refresh_decode.get('sub'):
                return {
                    "msg": "Access denied: Invalid user for the refresh token."
                }, 401  # Unauthorized
            
            # 
            user = AuthService.get_user_by_email(current_user_id)

            if user is None: 
                return {
                    "msg": "Access denied: Bad Token."
                }, 401 # Unauthorized
            
            # Check if the token is already blacklisted
            if TokenService.is_blacklisted(user, refresh_decode["jti"]) :
                    return {
                        "msg": "Access denied: Bad Token."
                    }, 401  # Unauthorized
            
            # Blacklist or revoke the previous access token
            previous_access_jti = get_jwt()['jti']   
            if previous_access_jti:
                TokenService.blacklist_token(None, 'access', user.id, get_jwt()['jti'], "Refresh token")

            # Generate a new access token with an extended expiration time
            access = create_access_token(identity=current_user_id)

            return {
                'access': access,
                'refresh': validated_data["refresh"],
                'msg': 'Access token refreshed successfully',
            }, 200 # OK
        except Exception as e:
            if hasattr(e, 'code') and e.code is not None:
                return {
                    'msg': str(e),
                }, e.code
            else:
                return {
                    'msg': 'An error occurred while processing your request.',
                    'error': str(e)
                }, 500  # Internal Server Error

# Define a route for activating a user's account operation.
@auth_namespace.route('/activate/<string:activation_token>')
class VerifyEmailResource(Resource):
    @auth_namespace.doc('Activate user account: verify email', security=None)
    def get(self, activation_token):
        """
        Email Verification Endpoint: Verify user's email address.

        This endpoint allows users to verify their email address by providing an activation token sent to their email.
        Upon successful verification, the user's email address is marked as verified, allowing them access to the application.

        :param activation_token: The activation token sent to the user's email address.
        :type activation_token: str

        :return: A JSON response indicating successful email verification or an error message if verification fails.
        """
        try:
            # Verify the activation token
            status, data = TokenService.verify_token(activation_token)

            if not status:
                # Token is invalid or expired
                return {
                    "msg": "Email verification failed",
                    "error": data
                }, 400 # Bad Request
            
            user = AuthService.get_user_by_email(data["email"])

            if user is not None and user.is_verified:
                return {
                    "msg": "Email is already verified"
                }, 200  # OK

            user.is_verified = True
            user.save()

            # Email is now verified
            return {
                "msg": "Email verified successfully"
            }, 200 # OK 
        
        except Exception as e:
            if hasattr(e, 'code') and e.code is not None:
                # If the exception has a status code attribute, return it
                return {
                    'msg': str(e),
                }, e.code
            else:
                # If no specific status code is provided, return a 500 Internal Server Error
                return {
                    'msg': 'An error occurred while processing your request.',
                    'error': str(e)
                }, 500  # Internal Server Error
            
# Define a route for resending verification emails operation.
@auth_namespace.route('/resend-email-verify')  # Define a route for the 'ResendVerificationEmailResource' endpoint
class ResendVerificationEmailResource(Resource): # Create a class for the 'ResendVerificationEmailResource' resource
    @auth_namespace.expect(EmailInput)  
    @auth_namespace.doc('Resend Verification Email endpoint', security=None)  # Add documentation for the endpoint.
    def post(self):# Define a POST method for handling the request
        """
        Resend Verification Email Endpoint: Resend a verification email to the user.

        This endpoint allows users to request the resending of a verification email.
        It checks if the user's email is already verified and sends a new verification email if not.

        :return: A response message indicating success or an error message.
        """
        try:  # Use a try-except block to handle potential exceptions
            # Get the request JSON data from the payload. 
            data = auth_namespace.payload  

            # Create an instance of the Marshmallow schema for input validation.
            email_schema = EmailSchema() 

             # Validate the input data using the schema instance.
            try:
                validated_data = email_schema.load(data)
            except ValidationError as err:
                # Handle validation errors by returning a 400 (Bad Request) response with error messages.
                return {
                    'msg': err.messages
                }, 400  # Bad Request
            
            user = AuthService.get_user_by_email(validated_data['email'])

            if user is None:
                return {
                    "msg": "User with this email does not exist."
                }, 404  # Not Found
            
            # Check if the account is already verified
            if user.is_verified:
                return {
                    'msg': 'Email is already verified.'
                }, 200  # OK
            
            # Generate an activation token
            activation_token = TokenService.generate_token(user.email, "account_activation") 
            # Generate the activation link dynamically
            activation_link = url_for('auth_verify_email_resource', activation_token=activation_token, _external=True)
            # 
            send_mail_status = MailService.send_email( 
                subject="Account Activation: Email Verification", 
                recipient = user.email, 
                message = render_template('activation_email.html', activation_link=activation_link),
                html=True
            )

            if send_mail_status : 
                return {
                    'msg': 'Failed to send password reset email.'
                }, 500  # Internal Server Error
            
            return {
                'msg': 'Verification email has been resent successfully.'
            }, 200  # OK
        except Exception as e:
            if hasattr(e, 'code') and e.code is not None:
                return {
                    'msg': str(e),
                }, e.code
            else:
                return {
                    'msg': 'An error occurred while processing your request.',
                    'error': str(e)
                }, 500

# Define a route for forgot password
@auth_namespace.route("/forgot-password") # Define a route for the 'ForgotPasswordResource' endpoint
class ForgotPasswordResource(Resource): # Create a class for the 'ForgotPasswordResource' resource
    @auth_namespace.expect(EmailInput)   
    @auth_namespace.doc(
        'Forgot Password Endpoint', 
        description='Initiate the password reset process by providing your email address',
        responses={
            200: 'Password reset instructions have been sent to your email',
            400: 'User does not exist',
            500: 'An error occurred while processing your request'
        }
    ) # Add documentation for the endpoint.
    def post(self):  # Define a POST method for handling the request 
        """
        Forgot Password Endpoint: Send a password reset email to the user.

        This endpoint allows users to request a password reset by providing their registered email address.

        :return: A response message indicating success or an error message.
        """
        try:  # Use a try-except block to handle potential exceptions
            # Get the request JSON data from the payload. 
            data = auth_namespace.payload  

            # Create an instance of the Marshmallow schema for input validation.
            email_schema = EmailSchema() 
            
            # Validate the input data using the schema instance.
            try:
                validated_data = email_schema.load(data)
            except ValidationError as err:
                # Handle validation errors by returning a 400 (Bad Request) response with error messages.
                return {
                    'msg': err.messages
                }, 200  # Bad Request

            user = AuthService.get_user_by_email(validated_data['email'])

            if user is None:
                return {
                    "msg": "User with this email does not exist."
                }, 200  # Not Found
            
            # Generate a unique password reset token 
            password_reset_token = TokenService.generate_token(user.email, "password_reset") 
            # Generate the activation link dynamically
            password_reset_token_link = url_for('auth_reset_password_resource', password_reset_token=password_reset_token, _external=True)
            # Send a password reset email to the user
            send_mail_status = MailService.send_email( 
                subject="Password Reset: Reset Your Password",
                recipient = user.email, 
                message = render_template('reset_password.html', password_reset_token_link=password_reset_token_link),
                html=True
            )
            
            if not send_mail_status : 
                return {
                    'msg': 'Failed to send password reset email.'
                }, 500  # Internal Server Error
             
            return {
                'msg': 'Password reset email has been sent.', 
            }, 200  # OK 
        except Exception as e:
            if hasattr(e, 'code') and e.code is not None:
                # If the exception has a status code attribute, return it
                return {
                    'msg': str(e),
                }, e.code
            else:
                # If no specific status code is provided, return a 500 Internal Server Error
                return {
                    'msg': 'An error occurred while processing your request.',
                    'error': str(e)
                }, 500  # Internal Server Error

# Define a route for reset password
@auth_namespace.route("/reset-password/<password_reset_token>")
class ResetPasswordResource(Resource):
    def post(self, password_reset_token):
        pass