import traceback

import argon2.exceptions


def log_message(message):
    log_file = open("free_encsoft_log.txt", "a")
    log_file.write(message + "\n")
    log_file.close()


def log_request_message(message):
    log_file = open("software_requests_log.txt", "a")
    log_file.write(message + "\n")
    log_file.close()



try:

    import os
    import sys
    from flask import Flask, jsonify
    from flask_sqlalchemy import SQLAlchemy
    from flask import request, jsonify
    from datetime import datetime, timedelta
    from dateutil.relativedelta import relativedelta
    from argon2 import PasswordHasher, exceptions

    import smtplib
    from email.mime.text import MIMEText
    from email.mime.multipart import MIMEMultipart

    import pymysql

    allowed_software_versions = ["v1.0","v1.1","v1.2"]

    # Email configuration - corrected settings


    SMTP_SERVER = "mail.encryptpro.net"
    SMTP_PORT = 465  # Changed from 587 to 465
    SENDER_EMAIL = "noreply@encryptpro.net"
    SENDER_PASSWORD = "encnoreply6437"  # Your actual password


    INTERNAL_SECRET = "ffdf9cf49f61679cbcd386904aa4fd8882b64a2cefadbac72d1b7a139438eb7c"

    '''
    database name: softaimm_Free_EncSoft
    
    
    '''

    try:
        connection = pymysql.connect(host='localhost', user='softaimm_python_sc_access', password='yQ3hTdj5T45fVSa',
                                     database='softaimm_Free_EncSoft', port=3306)
        log_message("Connection successful")
        connection.close()
    except pymysql.err.OperationalError as e:
        log_message("Connection failed: {}".format(e))

    app = Flask(__name__)
    ph = PasswordHasher()
    # Check like this ph.verify(stored_hash, client_hash)


    # Database connection details (replace with your actual values)
    db_user = 'softaimm_python_sc_access'  # e.g., 'softaimm_user'
    db_pass = 'yQ3hTdj5T45fVSa'  # The password from cPanel
    db_host = 'localhost'
    db_port = '3306'
    db_name = 'softaimm_Free_EncSoft'

    # Use pymysql as the connector
    app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+pymysql://{db_user}:{db_pass}@{db_host}:{db_port}/{db_name}'
    app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False



    # # WordPress database configuration
    # wp_db_name = 'softaimm_wp702'  # Replace with your actual WordPress database name
    # app.config['SQLALCHEMY_BINDS'] = {
    #     'wp': f'mysql+pymysql://{db_user}:{db_pass}@{db_host}:{db_port}/{wp_db_name}'
    # }

    db = SQLAlchemy(app)

    # Add this right after db = SQLAlchemy(app) to test connectivity
    with app.app_context():
        try:
            db.engine.connect()
            log_message("Main database connection successful!")
        except Exception as e:
            log_message("Main database connection failed: {}".format(e))

        # try:
        #     # Test WordPress database connection
        #     wp_connection = db.get_engine(bind='wp')
        #     wp_connection.connect()
        #     log_message("WordPress database connection successful!")
        # except Exception as e:
        #     log_message("WordPress database connection failed: {}".format(e))


    class WpUsers(db.Model):
        __bind_key__ = 'wp'  # This tells SQLAlchemy to use the WordPress database connection
        __tablename__ = 'wpul_users'

        ID = db.Column(db.BigInteger, primary_key=True, autoincrement=True)
        user_login = db.Column(db.String(60), nullable=False)
        user_pass = db.Column(db.String(255), nullable=False)
        user_nicename = db.Column(db.String(50), nullable=False)
        user_email = db.Column(db.String(100), nullable=False)
        user_url = db.Column(db.String(100), nullable=False)
        user_registered = db.Column(db.DateTime, nullable=False, default='0000-00-00 00:00:00')
        user_activation_key = db.Column(db.String(255), nullable=False)
        user_status = db.Column(db.Integer, nullable=False, default=0)
        display_name = db.Column(db.String(250), nullable=False)


    def check_wp_user_exists(username, email):
        """
        Check if a user exists in the WordPress database by username and email

        Args:
            username (str): The username to check
            email (str): The email to check

        Returns:
            bool: True if user exists, False otherwise
        """
        try:
            user = WpUsers.query.filter(
                (WpUsers.user_login == username) &
                (WpUsers.user_email == email)
            ).first()

            return user is not None
        except Exception as e:
            log_message(f"Error checking WordPress user: {e}")
            return False


    # Alternative method that returns the user object if found
    def get_wp_user(username, email):
        """
        Get WordPress user by username and email

        Args:
            username (str): The username to check
            email (str): The email to check

        Returns:
            WpUsers: User object if found, None otherwise
        """
        try:
            user = WpUsers.query.filter(
                (WpUsers.user_login == username) &
                (WpUsers.user_email == email)
            ).first()

            return user
        except Exception as e:
            log_message(f"Error getting WordPress user: {e}")
            return None


    class ServerSettings(db.Model):
        __tablename__ = 'ServerSettings'

        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        login_expairy = db.Column(db.Integer, nullable=False)


    class Users(db.Model):
        __tablename__ = 'Users'

        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        username = db.Column(db.String(50), unique=True, nullable=False)
        email = db.Column(db.String(100), unique=True, nullable=False)
        password_hash = db.Column(db.String(255), nullable=False)
        created_at = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp())
        updated_at = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(),
                               onupdate=db.func.current_timestamp())
        last_login_at = db.Column(db.TIMESTAMP)
        account_status = db.Column(db.Enum('active', 'inactive', 'suspended'), default='active')
        allowed_devices = db.Column(db.Integer)
        allowed_groups = db.Column(db.Integer)
        software_version = db.Column(db.String(20))
        notification = db.Column(db.Text)
        command = db.Column(db.String(50))
        is_deleted = db.Column(db.Boolean, default=False, server_default='0', nullable=False)


    class Plans(db.Model):
        __tablename__ = 'Plans'

        plan_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        name = db.Column(db.String(50), nullable=False)
        description = db.Column(db.Text)
        price = db.Column(db.Numeric(10, 2), nullable=False)
        days = db.Column(db.Numeric(10, 2), nullable=False)
        duration_months = db.Column(db.Integer, nullable=False)
        per_month_price = db.Column(db.Numeric(10, 2), nullable=False)
        status = db.Column(db.Enum('active', 'inactive'), default='active')
        created_at = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp())
        updated_at = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(),
                               onupdate=db.func.current_timestamp())
        discount_percentage = db.Column(db.Numeric(5, 2))

        def __repr__(self):
            return f'<Plan {self.plan_id}: {self.name}>'


    class Subscriptions(db.Model):
        __tablename__ = 'Subscriptions'

        sub_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        product_key = db.Column(db.String(50), unique=True, nullable=False)
        registered_email = db.Column(db.String(255), unique=True, nullable=True)
        user_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=True)
        last_bought_plan = db.Column(db.Integer, nullable=True)
        start_date = db.Column(db.Date, nullable=True)
        end_date = db.Column(db.Date, nullable=True)
        status = db.Column(db.Enum('inactive','active', 'cancelled', 'expired'), default='active')
        created_at = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp())
        updated_at = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(),
                               onupdate=db.func.current_timestamp())

        def __repr__(self):
            return f'<Subscription {self.sub_id} - User {self.user_id} - Plan {self.plan_id}>'


    class Devices(db.Model):
        __tablename__ = 'Devices'

        device_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        user_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=False)
        mcode_uuid = db.Column(db.String(50),nullable=True)
        mcode_serial = db.Column(db.String(50),nullable=True)
        hard_drive_code = db.Column(db.String(50),nullable=True)
        cpu_code = db.Column(db.String(50),nullable=True)
        changed_counter = db.Column(db.Integer)
        added_at = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp())
        last_used_at = db.Column(db.TIMESTAMP)
        status = db.Column(db.Enum('active', 'inactive'), default='active')

        def __repr__(self):
            return f'<Device {self.device_id} - User {self.user_id} - Status {self.status}>'


    class LoginHistory(db.Model):
        __tablename__ = 'LoginHistory'

        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        user_id = db.Column(db.Integer, db.ForeignKey('Users.id'), nullable=False)
        login_at = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp())
        success = db.Column(db.Text, nullable=False)  # Consider using Boolean if it's just true/false
        ip_address = db.Column(db.String(45), nullable=True)
        user_agent = db.Column(db.String(255), nullable=True)

        # Relationship to Users table (optional)
        user = db.relationship('Users', backref='login_history')

        def __repr__(self):
            return f'<LoginHistory {self.id} - User {self.user_id} - {"Success" if self.success else "Failed"}>'


    class Requests(db.Model):
        __tablename__ = 'Requests'

        username = db.Column(db.String(50), unique=True, nullable=False)
        request_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        email = db.Column(db.String(100), unique=True, nullable=False)
        request_datetime = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(),
                               onupdate=db.func.current_timestamp())
        status = db.Column(db.Enum('pending','approved', 'rejected_email', 'rejected_ip', 'rejected_duplicate'), default='pending')
        ip_address = db.Column(db.String(50), unique=False, nullable=False)
        affiliate_id = db.Column(db.String(50), unique=False, nullable=True)

    class ApprovedRequests(db.Model):
        __tablename__ = 'ApprovedRequests'

        username = db.Column(db.String(50), unique=True, nullable=False)
        email = db.Column(db.String(100), unique=True, nullable=False)
        request_id = db.Column(db.Integer, primary_key=True)
        product_key = db.Column(db.String(50), unique=True, nullable=False)
        approved_datetime = db.Column(db.TIMESTAMP, server_default=db.func.current_timestamp(),
                               onupdate=db.func.current_timestamp())
        ip_address = db.Column(db.String(50), unique=False, nullable=False)
        affiliate_id = db.Column(db.String(50), unique=False, nullable=True)


    class FreeProductKeys(db.Model):
        __tablename__ = 'FreeProductKeys'

        id = db.Column(db.Integer, primary_key=True, autoincrement=True)
        product_key = db.Column(db.String(50), unique=True, nullable=False)
        status = db.Column(db.Enum('active','inactive'), default='inactive')


    def send_email(recipient_email, subject, html_body):
        """
        Sends an email to the recipient using SMTP.

        Args:
            recipient_email (str): Email address of the recipient
            subject (str): Email subject line
            html_body (str): HTML content of the email body

        Returns:
            tuple: (success: bool, message: str)
        """
        try:
            log_request_message(f"Attempting to send email to {recipient_email}")

            # Create message
            message = MIMEMultipart('alternative')
            message['From'] = SENDER_EMAIL
            message['To'] = recipient_email
            message['Subject'] = subject

            # Attach HTML body
            html_part = MIMEText(html_body, 'html')
            message.attach(html_part)

            log_request_message(f"Connecting to SMTP server {SMTP_SERVER}:{SMTP_PORT}")

            # Connect to SMTP server and send email using SSL (port 465)
            with smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT, timeout=30) as server:
                log_request_message("Connected to SMTP server, attempting login")
                server.login(SENDER_EMAIL, SENDER_PASSWORD)
                log_request_message("Login successful, sending message")
                server.send_message(message)
                log_request_message("Message sent successfully")

            log_request_message(f"Email sent successfully to {recipient_email}")
            return (True, "Email sent successfully")

        except smtplib.SMTPAuthenticationError as e:
            error_msg = f"SMTP Authentication failed for {SENDER_EMAIL}: {str(e)}"
            log_request_message(f"ERROR: {error_msg}")
            return (False, error_msg)

        except smtplib.SMTPConnectError as e:
            error_msg = f"Failed to connect to SMTP server: {str(e)}"
            log_request_message(f"ERROR: {error_msg}")
            return (False, error_msg)

        except smtplib.SMTPException as e:
            error_msg = f"SMTP error occurred: {str(e)}"
            log_request_message(f"ERROR: {error_msg}")
            return (False, error_msg)

        except TimeoutError as e:
            error_msg = f"Connection timeout: {str(e)}"
            log_request_message(f"ERROR: {error_msg}")
            return (False, error_msg)

        except Exception as e:
            error_msg = f"Failed to send email: {str(e)}"
            log_request_message(f"ERROR: {error_msg}")
            import traceback
            log_request_message(f"TRACEBACK: {traceback.format_exc()}")
            return (False, error_msg)


    '''
    
    THIS IS FOR CREATING THE TABLES
    
    # # ADD THIS SECTION HERE - RIGHT AFTER ALL MODEL DEFINITIONS
    # with app.app_context():
    #     try:
    #         db.create_all()
    #         log_message("All tables created successfully!")
    #     except Exception as e:
    #         log_message("Failed to create tables: {}".format(e))
    
    '''


    def get_user_account_details(user_id):
        """
        Retrieve user details including their subscription information.

        Args:
            user_id (int): The ID of the user to look up

        Returns:
            tuple: (success: bool, result: dict/None)
                   success indicates if operation was successful
                   result contains data if successful, None otherwise
        """
        try:
            # Get the user
            user = Users.query.filter_by(id=user_id).first()

            if not user:
                return (True, None)  # User not found (this is a successful query)

            # Get active subscription (if any)
            active_subscription = Subscriptions.query.filter_by(
                user_id=user_id,
                status='active'
            ).first()

            # Prepare basic result
            result = {
                'username': user.username,
                'email': user.email,
                'plan_name': None,
                'expiry_date': None,
                'groups_allowed':user.allowed_groups,
                'devices_allowed':user.allowed_devices
            }

            if active_subscription:
                # Get plan details
                plan = Plans.query.get(active_subscription.last_bought_plan)
                if plan:
                    result['plan_name'] = plan.name
                if active_subscription.end_date:
                    result['expiry_date'] = active_subscription.end_date.isoformat()

            return (True, result)


        except Exception as e:

            log_message("Error in get_user_account_details: " + str(e))
            tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
            log_message("".join(tb_str))
            return (False, None)


    def get_login_expiry_hours():
        server_settings = ServerSettings.query.filter_by(id=1).first()
        login_expiry_hours = server_settings.login_expairy if server_settings else None
        return login_expiry_hours




    def is_product_key_available(product_key):
        """
        Check if a product key exists and is not associated with any user

        Args:
            product_key (str): The product key to validate

        Returns:
            bool: True if key exists and has no user, False otherwise
        """
        subscription = Subscriptions.query.filter_by(product_key=product_key).first()
        if subscription is not None and subscription.status == "inactive":
            return False, "Key is inactive"

        # we only proceed for active key
        if subscription is not None and subscription.user_id is None:
            return True, "Key is available"
        elif subscription is not None and subscription.user_id is not None:
            return False, "Key is already taken"
        else:
            return False, "Key Not Found"


    def add_user_record(username, email, password_hash, software_version=None, notification=None):
        """
        Creates a new user in the database.

        Args:
            username (str): Unique username
            email (str): Unique email
            password_hash (str): Pre-hashed password
            software_version (str, optional): Version of the software
            notification (str, optional): Initial notification message

        Returns:
            tuple: (success: bool, result: Users or str)
                   Where result is the user object if success=True,
                   or an error message if success=False
        """
        try:
            if Users.query.filter((Users.username == username) | (Users.email == email)).first():
                return (False, "Username or email already exists")



            new_user = Users(
                username=username,
                email=email,
                allowed_devices=1,
                allowed_groups=1,
                password_hash=password_hash,
                account_status='active',
                is_deleted=False,
                software_version=software_version,
                notification=notification
            )

            db.session.add(new_user)
            db.session.commit()
            return (True, new_user)

        except IntegrityError:
            db.session.rollback()
            return (False, "Username or email already exists")
        except Exception as e:
            db.session.rollback()
            return (False, f"An error occurred: {str(e)}")



    def assign_subscription_to_user(product_key, user_id, end_date):
        """
        Updates a subscription record with user_id and dates

        Args:
            product_key (str): The product key to update
            user_id (int): The user ID to assign
            end_date (date): The calculated end date of the subscription

        Returns:
            tuple: (success: bool, result: str/None)
                   success=True if updated successfully,
                   success=False if error occurred (result contains error message)
        """
        try:
            # Get today's date once
            today = datetime.today().date()

            # Find and update the subscription in one atomic operation
            updated = Subscriptions.query.filter_by(product_key=product_key).update({
                'user_id': user_id,
                'start_date': today,
                'end_date': end_date,
                'updated_at': datetime.now(),
                'status': 'active'  # Optional: ensure status is active
            })

            if updated == 0:
                return False, "Product key not found"

            db.session.commit()
            return True, "User successfully subscribed"

        except Exception as e:
            db.session.rollback()
            return False, f"Database error: {str(e)}"


    def add_device_record(user_id, mcode_uuid=None, mcode_serial=None, hard_drive_code=None, cpu_code=None):
        """
        Adds a new device record to the Devices table

        Args:
            user_id (int): The user ID this device belongs to
            mcode_uuid (str, optional):
            mcode_serial (str, optional):
            hard_drive_code (str, optional): Hard drive identifier
            cpu_code (str, optional): CPU identifier

        Returns:
            tuple: (success: bool, result: Devices/str)
                   success=True with device object if created successfully,
                   success=False with error message if failed
        """
        try:
            current_time = datetime.now()

            new_device = Devices(
                user_id=user_id,
                mcode_uuid = mcode_uuid,
                mcode_serial = mcode_serial,
                hard_drive_code=hard_drive_code,
                cpu_code=cpu_code,
                changed_counter=0,
                added_at=current_time,
                last_used_at=current_time,
                status='active'
            )

            db.session.add(new_device)
            db.session.commit()

            return True, new_device

        except Exception as e:
            db.session.rollback()
            return False, f"Error adding device: {str(e)}"


    def add_login_history(user_id, success, ip_address=None, user_agent=None):
        """
        Records a login attempt in the LoginHistory table

        """
        try:
            new_entry = LoginHistory(
                user_id=user_id,
                success=success,
                ip_address=ip_address,
                user_agent=user_agent
            )

            db.session.add(new_entry)
            db.session.commit()

            return True, new_entry

        except Exception as e:
            db.session.rollback()
            return False, f"Error recording login history: {str(e)}"


    def get_user_record_for_username_password(username, password_entry):
        """
        Fetch a user by username and password (plain text comparison).

        Args:
            username (str): Username to search for
            password (str): Plain-text password to match

        Returns:
            Users: The user SQLAlchemy model object if matched
            None: If no user found or password doesn't match
        """
        user = Users.query.filter_by(
            username=username,
            is_deleted=False
        ).first()

        if user:
            try:
                log_message("hash :{}||||{}||||".format(user.password_hash,password_entry))
                if ph.verify(user.password_hash, password_entry):

                    return True, user  # Returns None if no match

            except argon2.exceptions.VerifyMismatchError as e:
                log_message("Error in get_user_record_for_username_password: " + str(e))
                tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
                log_message("".join(tb_str))

                return False, "password mismatch error"

            except Exception as e:
                log_message("Error in get_user_record_for_username_password: " + str(e))
                tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
                log_message("".join(tb_str))
                return False, "error in get_user_record_for_username_password"
        else:
            return False, "username not found"


    def get_subscription_record_by_userid_and_productkey(user_id, product_key):
        """
        Fetch a subscription by user_id and product_key.

        Args:
            user_id (int): The user's ID
            product_key (str): The product key to match

        Returns:
            Subscriptions: The subscription SQLAlchemy model object if matched
            None: If no subscription found
        """
        subscription = Subscriptions.query.filter_by(
            user_id=user_id,
            product_key=product_key
        ).first()

        return subscription  # Returns None if no match


    def get_subscription_record_by_userid(user_id):
        """
        Fetch a subscription by user_id and product_key.

        Args:
            user_id (int): The user's ID
            product_key (str): The product key to match

        Returns:
            Subscriptions: The subscription SQLAlchemy model object if matched
            None: If no subscription found
        """
        subscription = Subscriptions.query.filter_by(
            user_id=user_id,
        ).first()

        return subscription  # Returns None if no match


    def get_active_devices_for_userid(user_id):
        """
        Fetch all active devices for a user.

        Args:
            user_id (int): The user's ID

        Returns:
            List[Devices]: List of active Device objects (empty if none found)
        """
        devices = Devices.query.filter_by(
            user_id=user_id,
            status='active'
        ).all()

        return devices  # Returns empty list if no matches


    def get_active_plan_by_plan_id(plan_id):
        """
        Fetch an active plan by its name.

        Args:
            plan_name (str): The name of the plan to search for

        Returns:
            Plans: The Plan object if found and active
            None: If no active plan matches the name
        """
        plan = Plans.query.filter_by(
            plan_id=plan_id,
            status='active'
        ).first()

        return plan


    def updating_tables_for_successful_login(user_id, device_id, success_message):
        """
        Adding a row in a login history
        devices - update row last_used_at where device_id matches
        login_history - add record with user_id, login_at, success
        users - update row last_login_at where user_id matches

        Returns:
            bool: True if all updates were successful
        """
        current_time = datetime.utcnow()

        try:
            # 1. Update device last_used_at
            device = Devices.query.filter_by(
                device_id=device_id,
                user_id=user_id,
                status='active'
            ).first()

            if device:
                device.last_used_at = current_time

            # 2. Create login history record
            login_record = LoginHistory(
                user_id=user_id,
                login_at=current_time,
                success=success_message,
                ip_address=None,
                user_agent=None
            )
            db.session.add(login_record)

            # 3. Update user last_login_at
            user = Users.query.get(user_id)
            if user:
                user.last_login_at = current_time

            db.session.commit()
            return True

        except Exception as e:
            db.session.rollback()
            # Log the error if needed
            print(f"Error updating login records: {str(e)}")
            return False


    def check_if_device_is_same(user_id, mcode_uuid, mcode_serial, hard_drive_code, cpu_code):
        # Query all active devices for the user
        devices = Devices.query.filter_by(
            user_id=user_id,
            status='active'
        ).all()

        # Prepare new codes dictionary
        new_codes = {
            'mcode_uuid': mcode_uuid,
            'mcode_serial': mcode_serial,
            'hard_drive_code': hard_drive_code,
            'cpu_code': cpu_code
        }

        for device in devices:
            # Create dictionary of device's non-None codes
            device_codes = {
                'mcode_uuid': device.mcode_uuid,
                'mcode_serial': device.mcode_serial,
                'hard_drive_code': device.hard_drive_code,
                'cpu_code': device.cpu_code
            }

            # Filter out None values from both device and new codes
            non_none_device_codes = {k: v for k, v in device_codes.items() if v is not None}
            non_none_new_codes = {k: v for k, v in new_codes.items() if v is not None}

            # Skip if no codes to compare
            if not non_none_device_codes:
                continue

            # Compare codes and count matches
            match_count = 0
            for code_name, device_value in non_none_device_codes.items():
                if code_name in non_none_new_codes and device_value == non_none_new_codes[code_name]:
                    match_count += 1

            # Calculate mismatch count
            total_compared = len(non_none_device_codes)
            mismatch_count = total_compared - match_count

            # Case 1: Full match (minimum 2 matches)
            if match_count >= 2 and mismatch_count == 0:
                # Update any None fields in device with new values
                updated = False
                for code_name in new_codes:
                    if getattr(device, code_name) is None and new_codes[code_name] is not None:
                        setattr(device, code_name, new_codes[code_name])
                        updated = True

                if updated:
                    try:
                        db.session.commit()
                    except Exception as e:
                        db.session.rollback()
                        # Log error but still return the device
                        log_message(f"Error updating device: {str(e)}")

                return True, device.device_id

            # Case 2: Zero match (no matches or >1 mismatch)
            elif match_count == 0 or mismatch_count > 1:
                # Skip this device
                continue

            # Case 3: One mismatch (exactly one mismatch)
            elif mismatch_count == 1:
                # Check changed counter
                if device.changed_counter >= 1:
                    # Skip if already changed once or more
                    continue

                # Update device with new values for None fields
                # whatever non none code is there in new codes needs to back propogate specially the changed one
                for code_name in new_codes:
                    if new_codes[code_name] is not None:
                        setattr(device, code_name, new_codes[code_name])

                # Increment changed counter
                device.changed_counter += 1

                try:
                    db.session.commit()
                except Exception as e:
                    db.session.rollback()
                    # Log error but still return the device
                    log_message(f"Error updating device: {str(e)}")

                return True, device.device_id

        # No matching device found
        return False, None


    def is_valid_email_domain(email):
        """
        Check if the email's domain matches one of the 50 authentic domains.

        Args:
            email (str): The email address to check.

        Returns:
            bool: True if the email's domain is in the authentic list, False otherwise.
        """
        # List of 50 authentic email domains
        valid_domains = [
            "gmail.com","majorgeeks.com", "filehorse.com","yahoo.com", "hotmail.com", "aol.com", "hotmail.co.uk",
            "hotmail.fr", "msn.com", "yahoo.fr", "wanadoo.fr", "orange.fr",
            "comcast.net", "yahoo.co.uk", "yahoo.com.br", "yahoo.co.in", "live.com",
            "rediffmail.com", "free.fr", "gmx.de", "web.de", "yandex.ru",
            "ymail.com", "libero.it", "outlook.com", "uol.com.br", "bol.com.br",
            "mail.ru", "cox.net", "hotmail.it", "sbcglobal.net", "sfr.fr",
            "live.fr", "verizon.net", "live.co.uk", "googlemail.com", "yahoo.es",
            "ig.com.br", "live.nl", "bigpond.com", "terra.com.br", "yahoo.it",
            "neuf.fr", "yahoo.de", "alice.it", "rocketmail.com", "att.net",
            "laposte.net", "facebook.com", "bellsouth.net", "yahoo.in", "hotmail.es"
        ]

        # Check if email is a string and contains '@'
        if not isinstance(email, str) or '@' not in email:
            return False

        # Extract domain from email (case-insensitive)
        try:
            domain = email.lower().split('@')[1]
            return domain in valid_domains
        except IndexError:
            return False



    # Test route
    @app.route('/')
    def index():
        return "Database connection setup complete!"


    # Server-side: Flask route to handle the registration
    # Category: ADMIN
    @app.route('/delete', methods=['POST'])
    def delete():

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403

        try:

            data = request.get_json()
            username = data.get('username')

            soft_delete_res, soft_delete_msg = deactivate_user_account(username)

            if soft_delete_res:
                return jsonify({"message": "User deleted successfully {}".format(soft_delete_msg)}), 200
            else:
                return jsonify({"error": soft_delete_msg}), 400

        except Exception as e:
            log_message("Error in Delete: " + str(e))
            tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
            log_message("".join(tb_str))


    # Server-side: Flask route to handle the registration
    # Category: ADMIN
    @app.route('/product_key', methods=['POST'])
    def register_new_product_key_route():

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403

        try:

            data = request.get_json()
            product_keys = data.get('product_key')

            # Validate input
            if not product_keys or not isinstance(product_keys, list):
                return jsonify({
                    'success': False,
                    'message': 'Missing or invalid product_keys list'
                }), 400

            seen_keys = set()
            duplicates_in_request = set()
            invalid_keys = []  # Track invalid keys (not starting with FREE)
            keys_to_add = []
            results = []

            # Check for duplicates and invalid keys in request
            for key in product_keys:
                # Check if key starts with "FREE"
                if not key.startswith('FREE'):
                    invalid_keys.append({
                        'key': key,
                        'success': False,
                        'message': 'Invalid key format - must start with FREE'
                    })
                    continue

                if key in seen_keys:
                    duplicates_in_request.add(key)
                    continue

                seen_keys.add(key)
                keys_to_add.append(key)

            # Add invalid keys to results early
            results.extend(invalid_keys)

            # Check existing keys in database
            existing_keys = set()
            if keys_to_add:  # Only query if there are valid keys
                existing_subs = Subscriptions.query.filter(
                    Subscriptions.product_key.in_(keys_to_add)
                ).all()

                if existing_subs:
                    existing_keys = {sub.product_key for sub in existing_subs}

            # Process valid keys
            new_subscriptions = []
            for key in keys_to_add:
                if key in existing_keys:
                    results.append({
                        'key': key,
                        'success': False,
                        'message': 'Key already exists in database'
                    })
                    continue

                new_sub = Subscriptions(
                    product_key=key,
                    status='inactive'
                )
                new_subscriptions.append(new_sub)
                results.append({
                    'key': key,
                    'success': True,
                    'message': 'Added successfully',
                    'subscription_id': new_sub.sub_id
                })

            # Bulk insert
            if new_subscriptions:  # Only commit if there are new subscriptions
                db.session.add_all(new_subscriptions)
                db.session.commit()

            # Add duplicate entries to results
            for key in duplicates_in_request:
                results.append({
                    'key': key,
                    'success': False,
                    'message': 'Duplicate in request'
                })

            return jsonify({
                'success': True,
                'message': 'Batch processing complete',
                'results': results,
                'added_count': len(new_subscriptions),
                'skipped_count': len(existing_keys) + len(duplicates_in_request) + len(invalid_keys)
            }), 201

        except Exception as e:
            log_message("Error in Register: " + str(e))
            tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
            log_message("".join(tb_str))

    ## jump here
    # Category: ADMIN
    @app.route('/activate_product_key', methods=['POST'])
    def activate_product_key_route():

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403

        try:
            data = request.get_json()
            product_key = data.get('product_key')
            plan_id = data.get('plan_id')
            email = data.get('email')

            # Validate required fields
            if not product_key or not plan_id or not email:
                return jsonify({
                    'success': False,
                    'message': 'Missing required fields (product_key, plan_id, email)'
                }), 400

            # Find the subscription
            subscription = Subscriptions.query.filter_by(product_key=product_key).first()
            if not subscription:
                return jsonify({
                    'success': False,
                    'message': 'Product key not found'
                }), 404

            # Find the subscription
            subscription_duplicate_email = Subscriptions.query.filter_by(registered_email=email).first()
            if subscription_duplicate_email:
                return jsonify({
                    'success': False,
                    'message': 'Email already registered with Product key :' + subscription_duplicate_email.product_key
                }), 401


            # Case 1: Key has no user assigned (new activation)
            if subscription.user_id is None:

                # Find plan by plan_id
                plan = Plans.query.filter_by(plan_id=plan_id, status='active').first()
                if not plan:
                    return jsonify({
                        'success': False,
                        'message': 'Plan not found or inactive'
                    }), 404

                try:
                    # Calculate dates
                    today = datetime.utcnow().date()

                    days_value = int(float(plan.days))  # Double conversion to handle Decimal
                    end_date = today + timedelta(days=days_value)

                    # Update subscription
                    subscription.user_id = None
                    subscription.last_bought_plan = plan_id
                    subscription.start_date = today
                    subscription.end_date = end_date
                    subscription.registered_email = email
                    subscription.status = 'active'
                    db.session.commit()

                except Exception as days_error:
                    log_message(
                        f"ERROR: Failed to calculate end date. Days value: {plan.days}. Error: {str(days_error)}")
                    log_message(f"TRACEBACK: {traceback.format_exc()}")
                    return jsonify({
                        'success': False,
                        'message': 'Invalid plan duration configuration'
                    }), 400


                return jsonify({
                    'success': True,
                    'message': 'Product key activated successfully',
                    'subscription_id': subscription.sub_id,
                    'start_date': today.isoformat(),
                    'end_date': end_date.isoformat(),
                    'days_active': plan.days
                }), 200

            # Case 2: Key already has a user assigned
            else:
                # Check if subscription is active
                if subscription.status == 'active':
                    return jsonify({
                        'success': False,
                        'message': 'Product key is already active'
                    }), 400

                # Check if subscription is expired
                today = datetime.utcnow().date()
                if subscription.end_date and subscription.end_date < today:
                    return jsonify({
                        'success': False,
                        'message': 'Product key has expired and cannot be activated'
                    }), 400

                # Reactivate inactive but not expired subscription
                subscription.status = 'active'
                db.session.commit()

                # Calculate days left
                days_left = (subscription.end_date - today).days

                return jsonify({
                    'success': True,
                    'message': 'Product key reactivated successfully',
                    'subscription_id': subscription.sub_id,
                    'days_left': days_left,
                    'new_end_date': subscription.end_date.isoformat()
                }), 200

        except SQLAlchemyError as e:
            log_message("Error in Register: " + str(e))
            db.session.rollback()
            return jsonify({
                'success': False,
                'message': f'Database error: {str(e)}'
            }), 500
        except Exception as e:
            tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
            log_message("".join(tb_str))
            return jsonify({
                'success': False,
                'message': f'Unexpected error: {str(e)}'
            }), 500


    # Category: INSOFT
    @app.route('/register_user_for_product_key', methods=['POST'])
    def register_new_user_for_product_key_route():

        try:

            '''
            Input:
            ·	Product key
            ·	Username
            ·	Password_hash
            ·	mcode_uuid
                mcode_serial
            ·	Hard drive code
            ·	CPU code

            '''

            login_expiry_hours = get_login_expiry_hours()

            data = request.get_json()
            product_key = data.get('product_key')
            username = data.get("username")
            password_hash = data.get('password_hash')
            mcode_uuid = data.get("mcode_uuid")
            mcode_serial = data.get("mcode_serial")
            hard_drive_code = data.get("hard_drive_code")
            cpu_code = data.get("cpu_code")
            # email = data.get("email")
            software_version = data.get("software_version")
            notification = "Thanks for logging in"
            nonce = data.get("nonce")

            '''
            Server Side Tasks
            ·	Validate the product key that its not associated with any user before
            ·	Add the record in the users table with the username, password_hash, account status (active), is_deleted (False)
            ·	Update Subscription table with that product key row with user_id, start_date (today), end_date according to plan months, updated_date (today)
            ·	Add the record in Devices table with user_id, mcode_uuid, mcode_serial, hard_drive_code, cpu_code, added_at (Today), last_used_at (Today), status (Active)
            ·	Add record in login history with user_id_login_at, success


            '''

            product_key_available_res, product_key_available_msg = is_product_key_available(product_key)

            if not product_key_available_res:
                return jsonify({"error": product_key_available_msg,"nonce":"","login_expiry_hours":0,
                                "login_date":""}), 400

            # Find the subscription because of email
            subscription = Subscriptions.query.filter_by(product_key=product_key).first()
            if not subscription:
                return jsonify({
                    'success': False,
                    'message': 'Product key not found'
                }), 404


            add_user_record_res , new_user_record = add_user_record(username,subscription.registered_email,password_hash,software_version,notification)

            if not add_user_record_res and new_user_record == "Username or email already exists":
                return jsonify({"result": "ERROR : Add record error {}".format(new_user_record),"nonce":"",
                                "login_expiry_hours":0,"login_date":""}), 401


            # now that the user record is added successfully we will set the user_id in the subscription record
            # Update the subscription with the new user_id
            subscription.user_id = new_user_record.id
            subscription.updated_at = datetime.utcnow()  # Update timestamp

            # Commit the changes to database
            db.session.commit()

            # now we add this device
            device_Record_res, device_Record = add_device_record(new_user_record.id,mcode_uuid,mcode_serial,hard_drive_code,cpu_code)
            if not device_Record:
                return jsonify({"result": "ERROR : Cannot add the device record","nonce":"",
                                "login_expiry_hours":0,"login_date":""}), 400

            # login history
            login_success = "New User Registered for Product Key Successfully"
            add_login_res, add_login_record = add_login_history(new_user_record.id,login_success)
            if not add_login_res:
                return jsonify({"result": "ERROR : Cannot add the login history record","nonce":"",
                                "login_expiry_hours":0,"login_date":""}), 400

            account_details_res , account_details = get_user_account_details(new_user_record.id)
            if account_details_res:

                account_details_username = account_details["username"]
                account_details_email = account_details["email"]
                account_details_plan_name = account_details["plan_name"]
                account_details_expiry_date = account_details["expiry_date"]
                account_details_groups_allowed = account_details["groups_allowed"]
                account_details_devices_allowed = account_details["devices_allowed"]

                return jsonify({"result": "SUCCESS : New user {} with ID {} Registered Successfully"
                                          "".format(new_user_record.id,new_user_record.username),
                                "nonce":nonce,"login_expiry_hours":login_expiry_hours,
                                "login_date": datetime.now().strftime("%Y-%m-%d"),
                                "account_details_username":account_details_username,
                                "account_details_email" : account_details_email,
                                "account_details_plan_name" : account_details_plan_name,
                                "account_details_expiry_date" : account_details_expiry_date,
                                "account_details_groups_allowed" : account_details_groups_allowed,
                                "account_details_devices_allowed" : account_details_devices_allowed}), 201

            else:
                raise Exception("Error in Account Details")

        except Exception as e:
            log_message("Error in Register: " + str(e))
            tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
            log_message("".join(tb_str))


    # Category: INSOFT
    @app.route('/validate_product_key', methods=['POST'])
    def validate_product_key_route():

        try:
            '''
            Input:
            ·	Product key

            '''
            data = request.get_json()
            product_key = data.get('product_key')
            nonce = data.get("nonce")

            '''
            Server Side Tasks:
            ·	Takes the key and checks if this key is valid or not
            ·	Then it checks
            ·	Product Key already has a user
                o	Product key user has reached the limit of devices
                    §	We tell the user to contact support
            o	Product key has a room for one more device
            §	We ask the user to enter username and password mentioning new device detected
            ·	Product key does not have a user
            o	We register the new user for this product key

            '''


            product_key_available_res, product_key_available_msg = is_product_key_available(product_key)
            # key is not even found
            if not product_key_available_res and product_key_available_msg == "Key Not Found":
                return jsonify({"result": "ERROR : {}".format(product_key_available_msg),"nonce":""}), 400

            if not product_key_available_res and product_key_available_msg == "Key is inactive":
                return jsonify({"result": "ERROR : {}".format(product_key_available_msg),"nonce":""}), 401

            # Key found but take with room for device
            if not product_key_available_res and product_key_available_msg == "Key is already taken":
                return jsonify({"result": "SUCCESS Key is found {}".format(product_key_available_msg),
                                "nonce":nonce}), 200

            # Key found and new user can be registered
            if product_key_available_res and product_key_available_msg == "Key is available":
                return jsonify({"result": "SUCCESS Key is found {}".format(product_key_available_msg),
                                "nonce":nonce}), 201

        except Exception as e:
            log_message("Error in Register: " + str(e))
            tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
            log_message("".join(tb_str))


    # Category: INSOFT
    @app.route('/old_user_new_device_login', methods=['POST'])
    def old_user_new_device_login_route():

        try:
            '''
            Input:
            ·	Product key
            ·	Username
            ·	Password_entry
            ·	Software_version
                mcode_uuid
                mcode_serial
            ·	Hard drive code
            ·	CPU code
            '''

            login_expiry_hours = get_login_expiry_hours()

            data = request.get_json()
            product_key = data.get('product_key')
            username = data.get("username")
            password_entry = data.get('password_entry')
            mcode_uuid = data.get("mcode_uuid")
            mcode_serial = data.get("mcode_serial")
            hard_drive_code = data.get("hard_drive_code")
            cpu_code = data.get("cpu_code")
            software_version = data.get("software_version")
            nonce = data.get("nonce")


            '''
            Server Side Tasks:
            ·	We first verify that this info is correct and all of the details (product key, username, password_hash) match for the user record
            ·	Then we check and identify if this login is from a new device or is it from the old device
            ·	Old device
                o	You are good to go we send the necessary files to the user and login successfully
            ·	New device
                o	Check if this user has the room for a new device then we login successfully after notifying the user that a new device is detected
                o	Otherwise we tell the user to contact support and the login is Unsuccessful

            '''

            # get the user_id from username and password_entry
            user_record_res, user_record = get_user_record_for_username_password(username,password_entry)
            if not user_record_res:
                if (user_record == "username not found") or (user_record == "password mismatch error"):
                    return jsonify({"result": "ERROR : " + user_record,
                                    "nonce": "", "login_date": "", "login_expiry_hours": 0}), 408
                else:
                    return jsonify({"result": "ERROR : "+user_record,
                                    "nonce":"","login_date":"","login_expiry_hours":0}), 400

            # now fetch the subscriber record for this user_id and product
            subscription_record = get_subscription_record_by_userid_and_productkey(user_record.id,product_key)
            if not subscription_record:
                return jsonify({"result": "ERROR : Subscriber record not found with user id and product key",
                                "nonce":"","login_date":"","login_expiry_hours":0}), 400

            if subscription_record.status != "active":
                return jsonify({"result": "ERROR : Subscriber record is not active",
                                "nonce":"","login_date":"","login_expiry_hours":0}), 400


            account_details_res , account_details = get_user_account_details(user_record.id)
            if account_details_res:

                account_details_username = account_details["username"]
                account_details_email = account_details["email"]
                account_details_plan_name = account_details["plan_name"]
                account_details_expiry_date = account_details["expiry_date"]
                account_details_groups_allowed = account_details["groups_allowed"]
                account_details_devices_allowed = account_details["devices_allowed"]

            else:
                raise Exception("Error in Account Details")


            # before we go ahead we check for software version and return 407 if old version
            if software_version not in allowed_software_versions:
                return jsonify({"result": "ERROR : Software Version Expired",
                                "nonce": "", "login_expiry_hours": 0, "login_date": ""}), 407

# jump here
            # get the list of active devices for this user id
            devices = get_active_devices_for_userid(user_record.id)
            # meaning we couldn't find any device active
            if not devices:
                # This means all of the devices of this user are deactivated
                # so we can add this new device and Login Successful

                device_record_res, device_record = add_device_record(user_record.id,mcode_uuid,mcode_serial,hard_drive_code,cpu_code)
                if device_record_res:
                    # now that the user has logged in successfully we need to update table
                    updating_tables_res = updating_tables_for_successful_login(user_record.id,device_record.device_id,"SUCCESS : New Device Added (1) for user")
                    if updating_tables_res:
                        return jsonify({"result": "SUCCESS : New Device Added (1)",
                                        "nonce":nonce,"login_expiry_hours":login_expiry_hours,
                                        "login_date": datetime.now().strftime("%Y-%m-%d"),
                                        "account_details_username": account_details_username,
                                        "account_details_email": account_details_email,
                                        "account_details_plan_name": account_details_plan_name,
                                        "account_details_expiry_date": account_details_expiry_date,
                                        "account_details_groups_allowed":account_details_groups_allowed,
                                        "account_details_devices_allowed":account_details_devices_allowed
                                        }), 200
                    else:
                        return jsonify({"result": "ERROR : Could not update the tables for successful login",
                                        "nonce":"","login_expiry_hours":0,"login_date":""}), 400
                else:
                    return jsonify({"result": "ERROR : Could not add the device record",
                                    "nonce":"","login_expiry_hours":0,"login_date":""}), 400

            else:
                # now first we need to check the allowed devices for this user subscription
                plan_record = get_active_plan_by_plan_id(subscription_record.last_bought_plan)
                if not plan_record:
                    return jsonify({"result": "ERROR : Plan record is not found for this subscription",
                                    "nonce":"","login_expiry_hours":0,"login_date":""}), 400

                # but before we begin we need to check if this is the same device or different device
                same_device, same_device_id = check_if_device_is_same(user_record.id,mcode_uuid,mcode_serial,hard_drive_code,cpu_code)

                devices_allowed = user_record.allowed_devices
                if (devices_allowed > len(devices)) and not same_device:
                    # this means we have a room for one more device
                    # so we add the device for this user
                    device_record_res, device_record = add_device_record(user_record.id, mcode_uuid,mcode_serial,
                                                                         hard_drive_code, cpu_code)
                    if device_record_res:
                        # now that the user has logged in successfully we need to update table
                        updating_tables_res = updating_tables_for_successful_login(user_record.id,
                                                                                   device_record.device_id,
                                                                                   "SUCCESS : New Device Added ({}) for user".format(len(devices)+1))
                        if updating_tables_res:
                            return jsonify({"result": "SUCCESS : New Device Added {}".format(len(devices)+1),
                                            "nonce":nonce,"login_expiry_hours":login_expiry_hours,
                                            "login_date": datetime.now().strftime("%Y-%m-%d"),
                                            "account_details_username": account_details_username,
                                            "account_details_email": account_details_email,
                                            "account_details_plan_name": account_details_plan_name,
                                            "account_details_expiry_date": account_details_expiry_date,
                                            "account_details_groups_allowed":account_details_groups_allowed,
                                            "account_details_devices_allowed":account_details_devices_allowed
                                            }), 201
                        else:
                            return jsonify({"result": "ERROR : Could not update the tables for successful login",
                                            "nonce":"","login_expiry_hours":0,"login_date":""}), 400
                    else:
                        return jsonify({"result": "ERROR : Could not add the device record",
                                        "nonce":"","login_expiry_hours":0,"login_date":""}), 400

                elif same_device:

                    updating_tables_res = updating_tables_for_successful_login(user_record.id,
                                                                               same_device_id,
                                                                               "SUCCESS : Old user logging in from same device")
                    if updating_tables_res:
                        return jsonify({"result": "SUCCESS : Old user logging in from same old active device",
                                       "nonce":nonce,"login_expiry_hours":login_expiry_hours,
                                        "login_date": datetime.now().strftime("%Y-%m-%d"),
                                        "account_details_username": account_details_username,
                                        "account_details_email": account_details_email,
                                        "account_details_plan_name": account_details_plan_name,
                                        "account_details_expiry_date": account_details_expiry_date,
                                        "account_details_groups_allowed":account_details_groups_allowed,
                                        "account_details_devices_allowed":account_details_devices_allowed
                                        }), 202
                    else:
                        return jsonify({"result": "ERROR : Could not update the tables for successful login",
                                        "nonce":"","login_expiry_hours":0,"login_date":""}), 400

                elif devices_allowed <= len(devices):
                    return jsonify({"result": "ERROR : Device Limit Reached",
                                    "nonce":"","login_expiry_hours":0,"login_date":""}), 403



        except Exception as e:
            log_message("Error in Register: " + str(e))
            tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
            log_message("".join(tb_str))


    # Category: INSOFT
    @app.route('/simple_login', methods=['POST'])
    def simple_login_route():

        try:
            '''
            Input:
            ·	Username
            ·	Password_entry
            ·	mcode_uuid
                mcode_serial
            ·	CPU_code
            ·	Hard_drive_code
            ·	Software_Version


            200 :   Same Device Login
            201 :   New Device Added (1) ANOMALY
            202 :   Simple Login New Devices Added

            401 :   Username or Password not found
            402 :   User subscriber record is not active
            403 :   Device Limit reached
            400 :   Exception


            '''

            login_expiry_hours = get_login_expiry_hours()

            data = request.get_json()
            username = data.get("username")
            password_entry = data.get('password_entry')
            mcode_uuid = data.get("mcode_uuid")
            mcode_serial = data.get("mcode_serial")
            hard_drive_code = data.get("hard_drive_code")
            cpu_code = data.get("cpu_code")
            software_version = data.get("software_version")
            nonce = data.get("nonce")

            '''
            Server Side Tasks:
            ·	We verify that we even have record for this Username and Password
            ·	Then we check if this user subscription is even active or not
            ·	Then we check if this device is even in the users already registered device
            .   If no devices are found to be active
                .   Then we add this device into table with ANOMALY message and Login Successfully
            ·	If its a same old device
                o	then we let the user login successfully
            ·	If its a new device
                o	Check if the user has room for a new device
                o	If yes
                    §	We login user successfully with new device detected message
                o	If No
                §	We tell the user to contact support with Login Unsuccessful
            '''

            # get the user_id from username and password_entry
            user_record_res, user_record = get_user_record_for_username_password(username,password_entry)
            if not user_record_res:
                return jsonify({"result": "ERROR : "+user_record,
                                "nonce":"","login_date":"","login_expiry_hours":0}), 401

            # now fetch the subscriber record for this user_id and product
            subscription_record = get_subscription_record_by_userid(user_record.id)
            if not subscription_record:
                return jsonify({"result": "ERROR : Subscriber record not found with user id and product key",
                                "nonce":"","login_date":"","login_expiry_hours":0}), 400

            if subscription_record.status != "active":
                return jsonify({"result": "ERROR : Subscriber record is not active",
                                "nonce":"","login_date":"","login_expiry_hours":0}), 402

            account_details_res , account_details = get_user_account_details(user_record.id)
            if account_details_res:

                account_details_username = account_details["username"]
                account_details_email = account_details["email"]
                account_details_plan_name = account_details["plan_name"]
                account_details_expiry_date = account_details["expiry_date"]
                account_details_groups_allowed = account_details["groups_allowed"]
                account_details_devices_allowed = account_details["devices_allowed"]

            else:
                raise Exception("Error in Account Details")

            # before we go ahead we check for software version and return 407 if old version
            if software_version not in allowed_software_versions:
                return jsonify({"result": "ERROR : Software Version Expired",
                                "nonce": "", "login_expiry_hours": 0, "login_date": ""}), 407


            # get the list of active devices for this user id
            devices = get_active_devices_for_userid(user_record.id)
            if not devices:
                # This means all of the devices of this user are deactivated but the user is trying to do
                # simple login we will allow the user to login successfully but add the message in the
                # login history as anomaly

                device_record_res, device_record = add_device_record(user_record.id,mcode_uuid, mcode_serial,hard_drive_code,cpu_code)
                if device_record_res:
                    # now that the user has logged in successfully we need to update table
                    updating_tables_res = updating_tables_for_successful_login(user_record.id,device_record.device_id,"SUCCESS : New Device Added (1) for user")
                    if updating_tables_res:
                        return jsonify({"result": "SUCCESS : ANOMALY (Simple Login) New Device Added (1)",
                                        "nonce":nonce,"login_expiry_hours":login_expiry_hours,
                                        "login_date": datetime.now().strftime("%Y-%m-%d"),
                                        "account_details_username": account_details_username,
                                        "account_details_email": account_details_email,
                                        "account_details_plan_name": account_details_plan_name,
                                        "account_details_expiry_date": account_details_expiry_date,
                                        "account_details_groups_allowed":account_details_groups_allowed,
                                        "account_details_devices_allowed":account_details_devices_allowed
                                        }), 201
                    else:
                        return jsonify({"result": "ERROR : ANOMALY (Simple Login) Could not update the tables for successful login",
                                        "nonce":"","login_date":"","login_expiry_hours":0}), 400
                else:
                    return jsonify({"result": "ERROR : ANOMALY (Simple Login) Could not add the device record",
                                    "nonce":"","login_date":"","login_expiry_hours":0}), 400

            else:
                # now first we need to check the allowed devices for this user subscription
                plan_record = get_active_plan_by_plan_id(subscription_record.last_bought_plan)
                if not plan_record:
                    return jsonify({"result": "ERROR : Plan record is not found for this subscription",
                                    "nonce":"","login_date":"","login_expiry_hours":0}), 400

                # but before we begin we need to check if this is the same device or different device
                same_device, same_device_id = check_if_device_is_same(user_record.id,mcode_uuid,mcode_serial,hard_drive_code,cpu_code)

                devices_allowed = user_record.allowed_devices

                if (devices_allowed > len(devices)) and not same_device:
                    # this means we have a room for one more device
                    # so we add the device for this user
                    device_record_res, device_record = add_device_record(user_record.id, mcode_uuid,mcode_serial,
                                                                         hard_drive_code, cpu_code)
                    if device_record_res:
                        # now that the user has logged in successfully we need to update table
                        updating_tables_res = updating_tables_for_successful_login(user_record.id,
                                                                                   device_record.device_id,
                                                                                   "SUCCESS : Simple Login New Device Added ({}) for user".format(len(devices)+1))
                        if updating_tables_res:
                            return jsonify({"result": "SUCCESS : Simple Login New Device Added {}".format(len(devices)+1),
                                            "nonce":nonce,"login_expiry_hours":login_expiry_hours,
                                            "login_date": datetime.now().strftime("%Y-%m-%d"),
                                            "account_details_username": account_details_username,
                                            "account_details_email": account_details_email,
                                            "account_details_plan_name": account_details_plan_name,
                                            "account_details_expiry_date": account_details_expiry_date,
                                            "account_details_groups_allowed":account_details_groups_allowed,
                                            "account_details_devices_allowed":account_details_devices_allowed
                                            }), 202
                        else:
                            return jsonify({"result": "ERROR : Simple Login Could not update the tables for successful login",
                                            "nonce":"","login_date":"","login_expiry_hours":0}), 400
                    else:
                        return jsonify({"result": "ERROR : Simple Login Could not add the device record",
                                        "nonce":"","login_date":"","login_expiry_hours":0}), 400


                elif same_device:

                    updating_tables_res = updating_tables_for_successful_login(user_record.id,
                                                                               same_device_id,
                                                                               "SUCCESS : Simple Login user logging in from same device")
                    if updating_tables_res:
                        return jsonify({"result": "SUCCESS : Simple Login Successful with already active device",
                                        "nonce":nonce,"login_expiry_hours":login_expiry_hours,
                                        "login_date": datetime.now().strftime("%Y-%m-%d"),
                                        "account_details_username": account_details_username,
                                        "account_details_email": account_details_email,
                                        "account_details_plan_name": account_details_plan_name,
                                        "account_details_expiry_date": account_details_expiry_date,
                                        "account_details_groups_allowed":account_details_groups_allowed,
                                        "account_details_devices_allowed":account_details_devices_allowed
                                        }), 200
                    else:
                        return jsonify({"result": "ERROR : Simple Login Could not update the tables for successful login",
                                        "nonce":"","login_date":"","login_expiry_hours":0}), 400


                elif devices_allowed <= len(devices):
                    return jsonify({"result": "ERROR : Simple Login Device Limit Reached",
                                    "nonce":"","login_date":"","login_expiry_hours":0}), 403



        except Exception as e:
            log_message("Error in Register: " + str(e))
            tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
            log_message("".join(tb_str))

    # Category: ADMIN
    @app.route('/cancel_subscription', methods=['POST'])
    def cancel_user_subscription():

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403

        try:
            data = request.get_json()

            # Validate at least one identifier is provided
            if not any([data.get('username'), data.get('user_id'), data.get('product_key')]):
                return jsonify({
                    'success': False,
                    'message': 'Must provide at least one identifier (username, user_id, or product_key)'
                }), 400

            subscription = None
            user = None

            # Case 1: Product key provided (most direct)
            if 'product_key' in data and data['product_key']:
                subscription = Subscriptions.query.filter_by(
                    product_key=data['product_key']
                ).first()

                if not subscription:
                    return jsonify({
                        'success': False,
                        'message': 'No subscription found with provided product key'
                    }), 404

            # Case 2: Username provided
            elif 'username' in data and data['username']:
                user = Users.query.filter_by(
                    username=data['username'],
                    is_deleted=False
                ).first()

                if not user:
                    return jsonify({
                        'success': False,
                        'message': 'User not found with provided username'
                    }), 404

                subscription = Subscriptions.query.filter_by(
                    user_id=user.id,
                    status='active'
                ).first()

                if not subscription:
                    return jsonify({
                        'success': False,
                        'message': 'No active subscription found for this user'
                    }), 404

            # Case 3: User ID provided
            elif 'user_id' in data and data['user_id']:
                user = Users.query.filter_by(
                    id=data['user_id'],
                    is_deleted=False
                ).first()

                if not user:
                    return jsonify({
                        'success': False,
                        'message': 'User not found with provided user ID'
                    }), 404

                subscription = Subscriptions.query.filter_by(
                    user_id=user.id,
                    status='active'
                ).first()

                if not subscription:
                    return jsonify({
                        'success': False,
                        'message': 'No active subscription found for this user'
                    }), 404

            # Update subscription status
            subscription.status = 'cancelled'
            subscription.updated_at = datetime.utcnow()

            # Update user's allowed devices to 0 if they exist
            if subscription.user_id:
                user = Users.query.get(subscription.user_id)
                if user:
                    user.allowed_devices = 0
                    user.updated_at = datetime.utcnow()

            db.session.commit()

            return jsonify({
                'success': True,
                'message': 'Subscription cancelled successfully',
                'subscription_id': subscription.sub_id,
                'product_key': subscription.product_key,
                'user_id': subscription.user_id,
                'cancelled_at': datetime.utcnow().isoformat()
            }), 200

        except SQLAlchemyError as e:
            db.session.rollback()
            return jsonify({
                'success': False,
                'message': 'Database error while cancelling subscription',
                'error': str(e)
            }), 500

        except Exception as e:
            return jsonify({
                'success': False,
                'message': 'Unexpected error while cancelling subscription',
                'error': str(e)
            }), 500


    # Category: ADMIN
    @app.route('/activate_subscription', methods=['POST'])
    def reactivate_subscription():

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403

        try:
            data = request.get_json()

            # Validate at least one identifier is provided
            if not any([data.get('username'), data.get('user_id'), data.get('product_key')]):
                return jsonify({
                    'success': False,
                    'message': 'Must provide at least one identifier (username, user_id, or product_key)'
                }), 400

            subscription = None
            user = None
            today = datetime.utcnow().date()

            # Case 1: Product key provided (most direct)
            if 'product_key' in data and data['product_key']:
                subscription = Subscriptions.query.filter_by(
                    product_key=data['product_key']
                ).first()

                if not subscription:
                    return jsonify({
                        'success': False,
                        'message': 'No subscription found with provided product key'
                    }), 404

            # Case 2: Username provided
            elif 'username' in data and data['username']:
                user = Users.query.filter_by(
                    username=data['username'],
                    is_deleted=False
                ).first()

                if not user:
                    return jsonify({
                        'success': False,
                        'message': 'User not found with provided username'
                    }), 404

                subscription = Subscriptions.query.filter_by(
                    user_id=user.id
                ).first()

                if not subscription:
                    return jsonify({
                        'success': False,
                        'message': 'No subscription found for this user'
                    }), 404

            # Case 3: User ID provided
            elif 'user_id' in data and data['user_id']:
                user = Users.query.filter_by(
                    id=data['user_id'],
                    is_deleted=False
                ).first()

                if not user:
                    return jsonify({
                        'success': False,
                        'message': 'User not found with provided user ID'
                    }), 404

                subscription = Subscriptions.query.filter_by(
                    user_id=user.id
                ).first()

                if not subscription:
                    return jsonify({
                        'success': False,
                        'message': 'No subscription found for this user'
                    }), 404

            # Check subscription status
            if subscription.status not in ['cancelled', 'expired']:
                return jsonify({
                    'success': False,
                    'message': f'Subscription is {subscription.status} and cannot be reactivated',
                    'current_status': subscription.status
                }), 400

            # Check expiry date
            if subscription.end_date and subscription.end_date < today:
                return jsonify({
                    'success': False,
                    'message': 'Subscription has expired and cannot be reactivated',
                    'end_date': subscription.end_date.isoformat(),
                    'current_date': today.isoformat()
                }), 400

            # Reactivate the subscription
            subscription.status = 'active'
            subscription.updated_at = datetime.utcnow()

            # Update user's allowed_devices if needed
            if subscription.user_id:
                user = user or Users.query.get(subscription.user_id)
                if user and user.allowed_devices == 0:
                    user.allowed_devices = 1  # Or your default value
                    user.updated_at = datetime.utcnow()

            db.session.commit()

            return jsonify({
                'success': True,
                'message': 'Subscription reactivated successfully',
                'subscription_id': subscription.sub_id,
                'product_key': subscription.product_key,
                'user_id': subscription.user_id,
                'end_date': subscription.end_date.isoformat(),
                'reactivated_at': datetime.utcnow().isoformat()
            }), 200

        except SQLAlchemyError as e:
            db.session.rollback()
            return jsonify({
                'success': False,
                'message': 'Database error while reactivating subscription',
                'error': str(e)
            }), 500

        except Exception as e:
            return jsonify({
                'success': False,
                'message': 'Unexpected error while reactivating subscription',
                'error': str(e)
            }), 500

#
#     @app.route('/set_allowed_devices', methods=['POST'])
#     def set_allowed_devices():
#         try:
#             data = request.get_json()
#             username = data.get('username')
#             new_allowed_devices = data.get('allowed_devices')
#
#             # Validate input
#             if not username or new_allowed_devices is None:
#                 return jsonify({
#                     'success': False,
#                     'message': 'Both username and allowed_devices are required'
#                 }), 400
#
#             if not isinstance(new_allowed_devices, int) or new_allowed_devices < 0:
#                 return jsonify({
#                     'success': False,
#                     'message': 'allowed_devices must be a positive integer'
#                 }), 400
#
#             # Find the user
#             user = Users.query.filter_by(
#                 username=username,
#                 is_deleted=False
#             ).first()
#
#             if not user:
#                 return jsonify({
#                     'success': False,
#                     'message': 'User not found'
#                 }), 404
#
#             # Store old value for response
#             old_allowed_devices = user.allowed_devices
#
#             # Check if we need to deactivate devices (only when reducing count)
#             if new_allowed_devices < old_allowed_devices:
#                 # Get active devices ordered by last_used_at (newest first) or added_at
#                 active_devices = Devices.query.filter_by(
#                     user_id=user.id,
#                     status='active'
#                 ).order_by(
#                     Devices.last_used_at.desc(),  # Most recently used first
#                     Devices.added_at.desc()  # Then by addition date
#                 ).all()
#
#                 # Calculate how many devices to deactivate
#                 devices_to_deactivate = len(active_devices) - new_allowed_devices
#
#                 if devices_to_deactivate > 0:
#                     # Deactivate the oldest/most inactive devices beyond the new limit
#                     for device in active_devices[new_allowed_devices:]:
#                         device.status = 'inactive'
#                         device.updated_at = datetime.utcnow()
#
#             # Update the allowed_devices count
#             user.allowed_devices = new_allowed_devices
#             user.updated_at = datetime.utcnow()
#
#             db.session.commit()
#
#             return jsonify({
#                 'success': True,
#                 'message': 'Allowed devices updated successfully',
#                 'username': username,
#                 'old_allowed_devices': old_allowed_devices,
#                 'new_allowed_devices': new_allowed_devices,
#                 'devices_deactivated': max(0, (
#                             old_allowed_devices - new_allowed_devices)) if new_allowed_devices < old_allowed_devices else 0
#             }), 200
#
#         except SQLAlchemyError as e:
#             db.session.rollback()
#             return jsonify({
#                 'success': False,
#                 'message': 'Database error while updating allowed devices',
#                 'error': str(e)
#             }), 500
#
#         except Exception as e:
#             return jsonify({
#                 'success': False,
#                 'message': 'Unexpected error while updating allowed devices',
#                 'error': str(e)
#             }), 500
#
#
#     @app.route('/set_allowed_groups', methods=['POST'])
#     def set_allowed_groups():
#         try:
#             data = request.get_json()
#             username = data.get('username')
#             new_allowed_groups = data.get('allowed_groups')
#
#             # Validate input
#             if not username or new_allowed_groups is None:
#                 return jsonify({
#                     'success': False,
#                     'message': 'Both username and allowed_groups are required'
#                 }), 400
#
#             if not isinstance(new_allowed_groups, int) or new_allowed_groups < 0:
#                 return jsonify({
#                     'success': False,
#                     'message': 'allowed_groups must be a positive integer'
#                 }), 400
#
#             # Find the user
#             user = Users.query.filter_by(
#                 username=username,
#                 is_deleted=False
#             ).first()
#
#             if not user:
#                 return jsonify({
#                     'success': False,
#                     'message': 'User not found'
#                 }), 404
#
#             # Store old value for response
#             old_allowed_groups = user.allowed_groups
#
#             # Update the allowed_groups count
#             user.allowed_groups = new_allowed_groups
#             user.updated_at = datetime.utcnow()
#
#             db.session.commit()
#
#             return jsonify({
#                 'success': True,
#                 'message': 'Allowed groups updated successfully',
#                 'username': username,
#                 'old_allowed_groups': old_allowed_groups,
#                 'new_allowed_groups': new_allowed_groups
#             }), 200
#
#         except SQLAlchemyError as e:
#             db.session.rollback()
#             return jsonify({
#                 'success': False,
#                 'message': 'Database error while updating allowed groups',
#                 'error': str(e)
#             }), 500
#
#         except Exception as e:
#             return jsonify({
#                 'success': False,
#                 'message': 'Unexpected error while updating allowed groups',
#                 'error': str(e)
#             }), 500
#

    # Category: ADMIN
    @app.route('/reset_devices', methods=['POST'])
    def reset_devices():

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403

        try:
            data = request.get_json()
            username = data.get('username')

            # Validate input
            if not username:
                return jsonify({
                    'success': False,
                    'message': 'Username is required'
                }), 400

            # Find the user
            user = Users.query.filter_by(
                username=username,
                is_deleted=False
            ).first()

            if not user:
                return jsonify({
                    'success': False,
                    'message': 'User not found'
                }), 404

            # Get all active devices for this user
            active_devices = Devices.query.filter_by(
                user_id=user.id,
                status='active'
            ).all()

            # If no active devices found
            if not active_devices:
                return jsonify({
                    'success': True,
                    'message': 'No active devices found for this user',
                    'username': username,
                    'devices_reset': 0
                }), 200

            # Mark all devices as inactive
            for device in active_devices:
                device.status = 'inactive'
                device.updated_at = datetime.utcnow()

            # Update the user's allowed_devices to 0
            user.allowed_devices = 0
            user.updated_at = datetime.utcnow()

            db.session.commit()

            return jsonify({
                'success': True,
                'message': f'Successfully reset {len(active_devices)} device(s)',
                'username': username,
                'devices_reset': len(active_devices),
                'allowed_devices_updated': 0
            }), 200

        except SQLAlchemyError as e:
            db.session.rollback()
            return jsonify({
                'success': False,
                'message': 'Database error while resetting devices',
                'error': str(e)
            }), 500

        except Exception as e:
            return jsonify({
                'success': False,
                'message': 'Unexpected error while resetting devices',
                'error': str(e)
            }), 500


#     @app.route('/renew_subscription', methods=['POST'])
#     def renew_subscription():
#         try:
#             data = request.get_json()
#             username = data.get('username')
#             product_key = data.get('product_key')
#             plan_id = data.get('plan_id')
#
#             # Validate at least one identifier is provided
#             if not username and not product_key:
#                 return jsonify({
#                     'success': False,
#                     'message': 'Must provide at least one identifier (username or product_key)'
#                 }), 400
#
#             if not plan_id:
#                 return jsonify({
#                     'success': False,
#                     'message': 'Plan ID is required'
#                 }), 400
#
#             # Find the plan
#             plan = Plans.query.filter_by(plan_id=plan_id, status='active').first()
#             if not plan:
#                 return jsonify({
#                     'success': False,
#                     'message': 'Plan not found or inactive'
#                 }), 404
#
#             subscription = None
#             user = None
#
#             # Case 1: Both username and product_key provided
#             if username and product_key:
#                 user = Users.query.filter_by(
#                     username=username,
#                     is_deleted=False
#                 ).first()
#
#                 if not user:
#                     return jsonify({
#                         'success': False,
#                         'message': 'User not found'
#                     }), 404
#
#                 subscription = Subscriptions.query.filter_by(
#                     product_key=product_key,
#                     user_id=user.id
#                 ).first()
#
#                 if not subscription:
#                     return jsonify({
#                         'success': False,
#                         'message': 'No subscription found with this product key for the specified user'
#                     }), 404
#
#             # Case 2: Only username provided
#             elif username:
#                 user = Users.query.filter_by(
#                     username=username,
#                     is_deleted=False
#                 ).first()
#
#                 if not user:
#                     return jsonify({
#                         'success': False,
#                         'message': 'User not found'
#                     }), 404
#
#                 subscription = Subscriptions.query.filter_by(
#                     user_id=user.id
#                 ).first()
#
#                 if not subscription:
#                     return jsonify({
#                         'success': False,
#                         'message': 'No subscription found for this user'
#                     }), 404
#
#             # Case 3: Only product_key provided
#             elif product_key:
#                 subscription = Subscriptions.query.filter_by(
#                     product_key=product_key
#                 ).first()
#
#                 if not subscription:
#                     return jsonify({
#                         'success': False,
#                         'message': 'No subscription found with this product key'
#                     }), 404
#
#             # Calculate new end date
#             today = datetime.utcnow().date()
#             days_to_add = int(float(plan.days))  # Convert Numeric to int
#
#             if subscription.end_date and subscription.end_date > today:
#                 # Case 1: Extend existing subscription
#                 new_end_date = subscription.end_date + timedelta(days=days_to_add)
#             else:
#                 # Case 2: Create new subscription period
#                 new_end_date = today + timedelta(days=days_to_add)
#
#             # Update subscription
#             subscription.status = 'active'
#             subscription.end_date = new_end_date
#             subscription.last_bought_plan = plan.plan_id
#             subscription.updated_at = datetime.utcnow()
#
#             # Update user's allowed devices if needed
#             if subscription.user_id:
#                 user = user or Users.query.get(subscription.user_id)
#                 if user and user.allowed_devices == 0:
#                     user.allowed_devices = 1  # Reset to default or your preferred value
#                     user.updated_at = datetime.utcnow()
#
#             db.session.commit()
#
#             return jsonify({
#                 'success': True,
#                 'message': 'Subscription renewed successfully',
#                 'subscription_id': subscription.sub_id,
#                 'product_key': subscription.product_key,
#                 'user_id': subscription.user_id,
#                 'plan_id': plan.plan_id,
#                 'new_end_date': new_end_date.isoformat(),
#                 'days_added': days_to_add,
#                 'renewed_at': datetime.utcnow().isoformat()
#             }), 200
#
#         except SQLAlchemyError as e:
#             db.session.rollback()
#             return jsonify({
#                 'success': False,
#                 'message': 'Database error while renewing subscription',
#                 'error': str(e)
#             }), 500
#
#         except Exception as e:
#             return jsonify({
#                 'success': False,
#                 'message': 'Unexpected error while renewing subscription',
#                 'error': str(e)
#             }), 500
#

    # Category: ADMIN
    @app.route('/fetch_user_details', methods=['POST'])
    def fetch_user_details():

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403

        data = request.get_json()
        username = data.get('username')
        product_key = data.get('product_key')

        # Validate input
        if not (username or product_key):
            return jsonify({
                'status': 'failed',
                'message': 'Must provide either username or product key'
            }), 400

        if username and product_key:
            return jsonify({
                'status': 'failed',
                'message': 'Provide only one identifier (username OR product key)'
            }), 400

        try:
            user = None
            subscription = None

            if username:
                # Find user by username
                user = Users.query.filter_by(username=username).first()
                if not user:
                    return jsonify({
                        'status': 'failed',
                        'message': f'User with username {username} not found'
                    }), 404

            if product_key:
                # Find subscription by product key
                subscription = Subscriptions.query.filter_by(product_key=product_key).first()
                if not subscription:
                    return jsonify({
                        'status': 'failed',
                        'message': f'Product key {product_key} not found'
                    }), 404

                # Get user from subscription
                if subscription.user_id:
                    user = Users.query.get(subscription.user_id)
                    if not user:
                        return jsonify({
                            'status': 'failed',
                            'message': f'User associated with product key {product_key} not found'
                        }), 404
                else:
                    return jsonify({
                        'status': 'failed',
                        'message': f'Product key {product_key} is not associated with any user'
                    }), 400

            # Get active devices
            active_devices = Devices.query.filter_by(
                user_id=user.id,
                status='active'
            ).all()

            # Get latest subscription if we didn't get it via product key
            if not subscription:
                subscription = Subscriptions.query.filter_by(
                    user_id=user.id
                ).order_by(Subscriptions.start_date.desc()).first()

            # Build response
            result = {
                'username': user.username,
                'email': user.email,
                'user_id': user.id,
                'last_login': user.last_login_at.isoformat() if user.last_login_at else None,
                'allowed_devices': user.allowed_devices,
                'allowed_groups': user.allowed_groups,
                'software_version': user.software_version,
                'active_devices': [{
                    'device_id': d.device_id,
                    'mcode_uuid': d.mcode_uuid,
                    'mcode_serial': d.mcode_serial,
                    'cpu_code': d.cpu_code,
                    'hard_drive_code': d.hard_drive_code
                } for d in active_devices],
                'subscription_details': {
                    'product_key': subscription.product_key if subscription else None,
                    'status': subscription.status if subscription else None,
                    'expiry_date': subscription.end_date.isoformat() if subscription and subscription.end_date else None,
                    'last_bought_plan': subscription.last_bought_plan if subscription else None
                }
            }

            return jsonify({
                'status': 'success',
                'data': result
            })

        except SQLAlchemyError as e:
            return jsonify({
                'status': 'failed',
                'message': f'Database error: {str(e)}'
            }), 500

        except Exception as e:
            return jsonify({
                'status': 'failed',
                'message': f'Unexpected error: {str(e)}'
            }), 500


    # Category: ADMIN
    @app.route('/fetch_all_available_keys', methods=['POST'])
    def fetch_all_available_keys():

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403

        try:
            # Get ONLY the product_key field for available keys
            available_keys = Subscriptions.query.with_entities(Subscriptions.product_key).filter(
                Subscriptions.registered_email.is_(None),
                Subscriptions.user_id.is_(None),
                Subscriptions.last_bought_plan.is_(None),
                Subscriptions.start_date.is_(None),
                Subscriptions.end_date.is_(None)
            ).all()

            # Extract just the strings from the results
            keys_list = [key[0] for key in available_keys]  # key[0] because it's a tuple

            return jsonify({
                'status': 'success',
                'available_keys': keys_list  # Just plain strings
            })

        except Exception as e:
            return jsonify({
                'status': 'failed',
                'message': str(e)
            }), 500


    # Category: ADMIN
    @app.route('/verify_user', methods=['POST'])
    def verify_user_route():

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403

        try:
            '''
            Input:
            ·	Username
            ·	Password_entry
            -   Nonce



            200 :   Username and Password matched User Found

            401 :   Username or Password not found
            400 :   Exception


            '''

            # login_expiry_hours = get_login_expiry_hours()

            data = request.get_json()
            username = data.get("username")
            password_entry = data.get('password_entry')
            nonce = data.get("nonce")

            '''
            Server Side Tasks:
            ·	We verify that we even have record for this Username and Password
            ·	Then we check if this user subscription is even active or not
            ·	Then we check if this device is even in the users already registered device
            .   If no devices are found to be active
                .   Then we add this device into table with ANOMALY message and Login Successfully
            ·	If its a same old device
                o	then we let the user login successfully
            ·	If its a new device
                o	Check if the user has room for a new device
                o	If yes
                    §	We login user successfully with new device detected message
                o	If No
                §	We tell the user to contact support with Login Unsuccessful
            '''

            # get the user_id from username and password_entry
            user_record_res, user_record = get_user_record_for_username_password(username, password_entry)
            if not user_record_res:
                return jsonify({"result": "ERROR : " + user_record,
                                "nonce": ""}), 401

            return jsonify({"result": "SUCCESS : User Verify SUccessfully",
                                "nonce": nonce}), 200



        except Exception as e:
            log_message("Error in Register: " + str(e))
            tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
            log_message("".join(tb_str))

            return jsonify({"result": "ERROR : Server Error Occurred",
                            "nonce": ""}), 400


    # Category: Website
    @app.route('/register_request', methods=['POST'])
    def register_request_route():
        '''
        Input
        username   email  status ip_address affiliate_id

        :return:
        '''

        try:
            # ── Shared secret verification ────────────────────────────
            incoming_secret = request.headers.get("X-Internal-Secret", "")
            if incoming_secret != INTERNAL_SECRET:
                log_request_message("BLOCKED: Invalid or missing X-Internal-Secret header")
                return jsonify({'success': False, 'message': 'Forbidden'}), 403

            data = request.get_json()
            email = data.get('email')
            username = email
            status = data.get("status", "pending")
            ip_address = data.get("ip_address")
            affiliate_id = data.get("affiliate_id")

            if True:

                # Validate required fields
                if not username or not email:
                    log_request_message(f"Missing required fields: username and email are required")

                    return jsonify({
                        'success': False,
                        'message': 'Missing required fields: username and email are required'
                    }), 400

                # # Check if username already exists
                # existing_username = Requests.query.filter_by(username=username).first()
                # if existing_username:
                #     return jsonify({
                #         'success': False,
                #         'message': 'Username already exists'
                #     }), 400

                # Check if email already exists
                existing_email = Requests.query.filter_by(email=email).first()
                if existing_email:

                    # we send the email

                    log_request_message(f"Already got request for {email} with username {existing_email.username}")

                    # TODO: send email with product key and further instructions

                    email_subject = "EncryptPro: Duplicate Request Detected"
                    email_html = f"""
                    <html>
                    <body>
                        <h2>Duplicate Request Notice</h2>
                        <p>We've detected multiple software requests from {email}.</p>
                        <p>Our system allows only one free product key per email address. Sending multiple requests from the same email is not permitted.</p>
                        <p>For any other assistance, please contact us at support@encryptpro.net</p>
                        <p>Best regards,<br>EncryptPro Team</p>
                    </body>
                    </html>
                    """

                    success, message = send_email(email, email_subject, email_html)
                    if success:
                        log_request_message(f"Duplicate email request Email sent successfully")
                    else:
                        log_request_message(f"Failed to send Duplicate email request Email to {email}: {message}")

                    return jsonify({
                        'success': False,
                        'message': 'Email already exists'
                    }), 409

                # Do not check for email domain all the domains should be allowed
                # if not is_valid_email_domain(email):
                #     log_request_message("> Invalid email domain")
                #
                #     # we send the email
                #     email_subject = "EncryptPro: Email Domain Issue"
                #     email_html = f"""
                #     <html>
                #     <body>
                #         <h2>Email Domain Not Recognized</h2>
                #         <p>We're unable to process your request with the email domain provided.</p>
                #         <p>The email domain appears to be unrecognized by our system. Please try again with a different email address from a recognized provider.</p>
                #         <p>If you believe your email is valid, please contact our support team at support@encryptpro.net for assistance.</p>
                #         <p>Best regards,<br>EncryptPro Team</p>
                #     </body>
                #     </html>
                #     """
                #
                #     success, message = send_email(email, email_subject, email_html)
                #     if success:
                #         log_request_message(f"Unsupported email domain Email sent successfully")
                #     else:
                #         log_request_message(f"Failed to send Unsupported email domain Email to {email}: {message}")
                #
                #     return jsonify({
                #         'success': False,
                #         'message': 'failed'
                #     }), 400

                # this means the email domain is also valid
                # now we validate this ip address

                # Check if ip address already exists
                existing_user_with_ip = Requests.query.filter_by(ip_address=ip_address).first()
                if existing_user_with_ip:
                    log_request_message("> IP Address check failed. {} duplicate ip {}.".format(existing_user_with_ip.username,existing_user_with_ip.ip_address))

                    # we sent email
                    email_subject = "EncryptPro: Multiple Requests Detected - Policy Violation"
                    email_html = f"""
                    <html>
                    <body>
                        <h2>Multiple Requests Detected</h2>
                        <p>Our system has detected multiple free software key requests originating from your system.</p>
                        <p>This activity violates our one-key-per-system policy and is strictly prohibited.</p>
                        <p><strong>No additional product keys will be issued to your system.</strong></p>
                        <p>This decision is final and in accordance with our terms of service.</p>
                        <p>Best regards,<br>EncryptPro Team</p>
                    </body>
                    </html>
                    """

                    success, message = send_email(email, email_subject, email_html)
                    if success:
                        log_request_message(f"Multiple requests detected Email sent successfully")
                    else:
                        log_request_message(f"Failed to send Multiple requests detected Email to {email}: {message}")

                    return jsonify({
                        'success': False,
                        'message': 'failed'
                    }), 429

                '''
                here we can now actually approve the request by
                > check if we even have keys left
                > add the record in the Requests table
                > selecting the free product key
                > activating the product key
                > adding record into approved requests table
                > send email with product key and further instructions
                '''

                # Select the top most inactive product key from FreeProductKeys table
                free_key = FreeProductKeys.query.filter_by(status='inactive').first()

                if not free_key:
                    log_request_message("Need to add more keys")

                    # we send email here to check back in 24 hours
                    email_subject = "Temporary Unavailability - EncryptPro Free Keys"
                    email_html = f"""
                    <html>
                    <body>
                        <h2>Temporarily Out of Free Keys</h2>
                        <p>Thank you for your interest in EncryptPro!</p>
                        <p>We apologize, but no free product keys are currently available. Our system replenishes free keys daily at 0:00 UTC.</p>
                        <p>Please check back in 24 hours when new keys become available.</p>
                        <p>If you continue to see this message after 24 hours, please contact support@encryptpro.net for assistance.</p>
                        <p>Thank you for your patience and interest in EncryptPro!</p>
                        <p>Best regards,<br>EncryptPro Team</p>
                    </body>
                    </html>
                    """

                    success, message = send_email(email, email_subject, email_html)
                    if success:
                        log_request_message(f"EncryptPro Free Keys Unavailable Email sent successfully")
                    else:
                        log_request_message(f"Failed to send EncryptPro Free Keys Unavailable Email to {email}: {message}")

                    return jsonify({
                        'success': False,
                        'message': 'No free keys available'
                    }), 503



                # Create new request
                new_request = Requests(
                    username=username,
                    email=email,
                    status=status,
                    ip_address=ip_address,
                    affiliate_id=affiliate_id
                )

                # Add to database
                db.session.add(new_request)
                db.session.commit()

                log_request_message(">>> Req {}\t:\t{} ".format(username,email))

                # Get the product key
                product_key = free_key.product_key
                email = data.get('email')  # Assuming email is in the request data
                plan_id = '1'  # Set your default plan_id for free keys

                # Find the subscription for this product key
                subscription = Subscriptions.query.filter_by(product_key=product_key).first()
                if not subscription:
                    log_request_message(f"ANOMALY: Product key {product_key} not found in Subscriptions table")
                    return jsonify({
                        'success': False,
                        'message': 'Product key not found in subscriptions'
                    }), 404

                # Check if email is already registered
                subscription_duplicate_email = Subscriptions.query.filter_by(registered_email=email).first()
                if subscription_duplicate_email:
                    log_request_message(
                        f"ANOMALY: Email {email} already registered with Product key: {subscription_duplicate_email.product_key}")
                    return jsonify({
                        'success': False,
                        'message': 'Email already registered with Product key: ' + subscription_duplicate_email.product_key
                    }), 208

                # Find plan by plan_id
                plan = Plans.query.filter_by(plan_id=plan_id, status='active').first()
                if not plan:
                    log_request_message(f"ANOMALY: Plan {plan_id} not found or inactive")
                    return jsonify({
                        'success': False,
                        'message': 'Plan not found or inactive'
                    }), 404

                try:
                    # Calculate dates
                    today = datetime.utcnow().date()
                    days_value = int(float(plan.days))  # Double conversion to handle Decimal
                    end_date = today + timedelta(days=days_value)

                    # Update subscription
                    subscription.user_id = None
                    subscription.last_bought_plan = plan_id
                    subscription.start_date = today
                    subscription.end_date = end_date
                    subscription.registered_email = email
                    subscription.status = 'active'

                    # Update FreeProductKeys status to active
                    free_key.status = 'active'

                    # Commit both changes
                    db.session.commit()

                    log_request_message(f"Product key {product_key} activated successfully for email {email}")

                    # TODO: adding record into approved requests table
                    # Get the request record to fetch request_id and other details
                    request_record = Requests.query.filter_by(email=email).first()
                    if not request_record:
                        log_request_message(f"ANOMALY: Request record not found for email {email}")
                        db.session.rollback()
                        return jsonify({
                            'success': False,
                            'message': 'Request record not found'
                        }), 404

                    # Create new approved request record
                    approved_request = ApprovedRequests(
                        username=request_record.username,
                        email=request_record.email,
                        request_id=request_record.request_id,
                        product_key=product_key,
                        ip_address=request_record.ip_address,
                        affiliate_id=request_record.affiliate_id
                    )

                    # Add to session
                    db.session.add(approved_request)

                    log_request_message(f"Approved request created for {email} with product key {product_key}")

                    # now we also need to change the status to approved of this request in the Requests table
                    request_record.status = 'approved'

                    # Commit all changes (approved request + status update)
                    db.session.commit()

                    # TODO: send email with product key and further instructions

                    email_subject = "Your EncryptPro Product Key & Getting Started Guide"
                    email_html = f"""
                    <html>
                    <body>
                        <h2>EncryptPro Request Approved!</h2>
                        <p>Dear {username},</p>
                        <p>Your software request has been approved. Your product key is now activated and ready to use.</p>
                        <p><strong>Product Key:</strong> {product_key}</p>

                        <h3>Getting Started Tutorials:</h3>
                        <ul>
                            <li><a href="[]">Complete Feature Tutorial Playlist</a></li>
                            <li><a href="[]">Free Software Installation Guide</a></li>
                        </ul>

                        <p>Need assistance? Contact our support team at support@encryptpro.net</p>
                        <p>Best regards,<br>EncryptPro Team</p>
                    </body>
                    </html>
                    """

                    success, message = send_email(email, email_subject, email_html)
                    if success:
                        log_request_message(f"Email sent to {email} with product key {product_key}")
                    else:
                        log_request_message(f"Failed to send email to {email}: {message}")

                    return jsonify({
                        'success': True,
                        'message': 'Product key activated successfully',
                        'product_key': product_key,
                        'subscription_id': subscription.sub_id,
                        'start_date': today.isoformat(),
                        'end_date': end_date.isoformat(),
                        'days_active': plan.days
                    }), 200

                except Exception as e:
                    db.session.rollback()
                    log_request_message(f"ERROR: Failed to activate product key. Error: {str(e)}")
                    log_request_message(f"TRACEBACK: {traceback.format_exc()}")
                    return jsonify({
                        'success': False,
                        'message': 'Failed to activate product key'
                    }), 500


            else:
                return jsonify({
                    'success': False,
                    'message': 'failed'
                }), 401

        except Exception as e:
            db.session.rollback()
            return jsonify({
                'success': False,
                'message': f'Error registering request: {str(e)}'
            }), 500


    # Category: ADMIN
    @app.route('/add_keys', methods=['POST'])
    def add_keys_route():


        """
        Adds multiple free product keys to both FreeProductKeys and Subscriptions tables.

        Expected JSON input:
        {
            "product_keys": ["KEY001", "KEY002", "KEY003"],
            "secret": "your_secret_key_here"
        }

        Returns JSON with:
        - success: bool
        - added_count: number of keys successfully added
        - skipped_count: number of keys skipped (duplicates)
        - duplicate_keys: list of duplicate keys
        - added_keys: list of successfully added keys
        - message: status message
        """

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403

        try:
            data = request.get_json()
            product_keys = data.get('product_keys')
            secret = data.get('secret')

            # Validate secret key
            if secret != "sdjfu43":
                return jsonify({
                    'success': False,
                    'added_count': 0,
                    'skipped_count': 0,
                    'duplicate_keys': [],
                    'added_keys': [],
                    'message': 'Invalid secret key'
                }), 401

            # Validate input
            if not product_keys or not isinstance(product_keys, list):
                return jsonify({
                    'success': False,
                    'added_count': 0,
                    'skipped_count': 0,
                    'duplicate_keys': [],
                    'added_keys': [],
                    'message': 'Invalid or empty product_keys list'
                }), 400

            seen_keys = set()
            duplicates_in_request = []
            keys_to_add = []

            # Remove duplicates within the request itself
            for key in product_keys:
                if key in seen_keys:
                    duplicates_in_request.append(key)
                    continue
                seen_keys.add(key)
                keys_to_add.append(key)

            # Check existing keys in FreeProductKeys database
            existing_free_keys = set()
            duplicate_keys = []

            if keys_to_add:
                existing_free_records = FreeProductKeys.query.filter(
                    FreeProductKeys.product_key.in_(keys_to_add)
                ).all()

                if existing_free_records:
                    existing_free_keys = {record.product_key for record in existing_free_records}

            # Check existing keys in Subscriptions database
            existing_subscription_keys = set()

            if keys_to_add:
                existing_subs = Subscriptions.query.filter(
                    Subscriptions.product_key.in_(keys_to_add)
                ).all()

                if existing_subs:
                    existing_subscription_keys = {sub.product_key for sub in existing_subs}

            # Process keys and create new records
            new_free_records = []
            new_subscriptions = []
            added_keys = []

            for key in keys_to_add:
                # Skip if exists in either table
                if key in existing_free_keys or key in existing_subscription_keys:
                    duplicate_keys.append(key)
                    continue

                # Create FreeProductKeys record
                new_free_record = FreeProductKeys(
                    product_key=key,
                    status='inactive'
                )
                new_free_records.append(new_free_record)

                # Create Subscriptions record
                new_sub = Subscriptions(
                    product_key=key,
                    status='inactive'
                )
                new_subscriptions.append(new_sub)

                added_keys.append(key)

            # Add duplicates from request to the duplicate list
            duplicate_keys.extend(duplicates_in_request)

            # Bulk insert for both tables
            if new_free_records and new_subscriptions:
                db.session.add_all(new_free_records)
                db.session.add_all(new_subscriptions)
                db.session.commit()

            added_count = len(added_keys)
            skipped_count = len(duplicate_keys)

            return jsonify({
                'success': True,
                'added_count': added_count,
                'skipped_count': skipped_count,
                'duplicate_keys': duplicate_keys,
                'added_keys': added_keys,
                'message': f'Successfully added {added_count} keys to both tables, skipped {skipped_count} duplicates'
            }), 201

        except IntegrityError:
            db.session.rollback()
            return jsonify({
                'success': False,
                'added_count': 0,
                'skipped_count': 0,
                'duplicate_keys': [],
                'added_keys': [],
                'message': 'Database integrity error occurred'
            }), 500
        except Exception as e:
            db.session.rollback()
            return jsonify({
                'success': False,
                'added_count': 0,
                'skipped_count': 0,
                'duplicate_keys': [],
                'added_keys': [],
                'message': f'An error occurred: {str(e)}'
            }), 500


    # Category: ADMIN
    @app.route('/keys_report', methods=['GET'])
    def keys_report_route():
        """
        Generates a report on subscriptions.

        Returns JSON with:
        - success: bool
        - total_subscriptions: total number of subscriptions
        - active_subscriptions: number of active subscriptions
        - inactive_subscriptions: number of inactive subscriptions
        - cancelled_subscriptions: number of cancelled subscriptions
        - latest_active_keys: list of 10 most recently updated active subscriptions
        - message: status message
        """

        secret = request.headers.get('X-Internal-Secret')
        if not secret or secret != INTERNAL_SECRET:
            return jsonify({'success': False, 'message': 'Forbidden'}), 403


        try:
            # Get total count
            total_subscriptions = Subscriptions.query.count()

            # Get status counts
            active_subscriptions = Subscriptions.query.filter_by(status='active').count()
            inactive_subscriptions = Subscriptions.query.filter_by(status='inactive').count()
            cancelled_subscriptions = Subscriptions.query.filter_by(status='cancelled').count()

            # Get latest 10 active subscriptions ordered by updated_at
            latest_active = Subscriptions.query.filter_by(status='active') \
                .order_by(Subscriptions.updated_at.desc()) \
                .limit(10) \
                .all()

            # Format the latest active subscriptions
            latest_active_keys = []
            for sub in latest_active:
                latest_active_keys.append({
                    'email': sub.registered_email,
                    'user_id': sub.user_id,
                    'product_key': sub.product_key,
                    'updated_at': sub.updated_at.strftime('%Y-%m-%d %H:%M:%S') if sub.updated_at else None
                })

            return jsonify({
                'success': True,
                'total_subscriptions': total_subscriptions,
                'active_subscriptions': active_subscriptions,
                'inactive_subscriptions': inactive_subscriptions,
                'cancelled_subscriptions': cancelled_subscriptions,
                'latest_active_keys': latest_active_keys,
                'message': 'Report generated successfully'
            }), 200

        except Exception as e:
            return jsonify({
                'success': False,
                'total_subscriptions': 0,
                'active_subscriptions': 0,
                'inactive_subscriptions': 0,
                'cancelled_subscriptions': 0,
                'latest_active_keys': [],
                'message': f'An error occurred: {str(e)}'
            }), 500


    if __name__ == '__main__':
        app.run()



except Exception as e:
    log_message("An error occurred: " + str(e))
    tb_str = traceback.format_exception(etype=type(e), value=e, tb=e.__traceback__)
    log_message("".join(tb_str))
