import os
import sys
from cryptography.fernet import Fernet
import string
import secrets
from passlib.hash import argon2
import psycopg
from argon2 import PasswordHasher
from argon2.exceptions import HashingError
import logging
import random

# Generate a random password
def generate_random_password(length=12):
    characters = string.ascii_letters + string.digits + string.punctuation
    return ''.join(random.choice(characters) for i in range(length))

# Hash the password using Argon2
def hash_password(password):
    ph = PasswordHasher()
    try:
        return ph.hash(password)
    except HashingError as e:
        print(f"Error hashing password: {e}")
        return None

# Set up basic configuration for logging
logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(levelname)s - %(message)s')

# Append the pinepods directory to sys.path for module import
sys.path.append('/pinepods')

try:
    # Attempt to import additional modules
    import database_functions.functions
    # import Auth.Passfunctions

    def hash_password(password: str):
        # Hash the password
        hashed_password = argon2.hash(password)
        # Argon2 includes the salt in the hashed output
        return hashed_password



    # Database variables
    db_host = os.environ.get("DB_HOST", "127.0.0.1")
    db_port = os.environ.get("DB_PORT", "5432")
    db_user = os.environ.get("DB_USER", "postgres")
    db_password = os.environ.get("DB_PASSWORD", "password")
    db_name = os.environ.get("DB_NAME", "pypods_database")

    # Function to create the database if it doesn't exist
    def create_database_if_not_exists():
        try:
            # Connect to the default 'postgres' database
            with psycopg.connect(
                host=db_host,
                port=db_port,
                user=db_user,
                password=db_password,
                dbname='postgres'
            ) as conn:
                conn.autocommit = True
                with conn.cursor() as cur:
                    # Check if the database exists
                    cur.execute("SELECT 1 FROM pg_database WHERE datname = %s", (db_name,))
                    exists = cur.fetchone()
                    if not exists:
                        logging.info(f"Database {db_name} does not exist. Creating...")
                        print(f"Database {db_name} does not exist. Creating...")
                        cur.execute(f"CREATE DATABASE {db_name}")
                        logging.info(f"Database {db_name} created successfully.")
                    else:
                        logging.info(f"Database {db_name} already exists.")
        except Exception as e:
            logging.error(f"Error creating database: {e}")
            raise

    # Create the database if it doesn't exist
    create_database_if_not_exists()

    # Create database connector
    cnx = psycopg.connect(
        host=db_host,
        port=db_port,
        user=db_user,
        password=db_password,
        dbname=db_name
    )

    # create a cursor to execute SQL statements
    cursor = cnx.cursor()

    def ensure_usernames_lowercase(cnx):
        with cnx.cursor() as cursor:
            cursor.execute('SELECT UserID, Username FROM "Users"')
            users = cursor.fetchall()
            for user_id, username in users:
                if username != username.lower():
                    cursor.execute('UPDATE "Users" SET Username = %s WHERE UserID = %s', (username.lower(), user_id))
                    print(f"Updated Username for UserID {user_id} to lowercase")

    def add_column_if_not_exists(cursor, table_name, column_name, column_definition):
        cursor.execute(f"""
            SELECT COUNT(*)
            FROM information_schema.columns
            WHERE table_name='{table_name}'
            AND column_name='{column_name}';
        """)
        if cursor.fetchone()[0] == 0:
            cursor.execute(f"""
                ALTER TABLE "{table_name}"
                ADD COLUMN {column_name} {column_definition};
            """)
            print(f"Column '{column_name}' added to table '{table_name}'")
        else:
            return

    # Create Users table first
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS "Users" (
            UserID SERIAL PRIMARY KEY,
            Fullname VARCHAR(255),
            Username VARCHAR(255) UNIQUE,
            Email VARCHAR(255),
            Hashed_PW VARCHAR(500),
            IsAdmin BOOLEAN,
            Reset_Code TEXT,
            Reset_Expiry TIMESTAMP,
            MFA_Secret VARCHAR(70),
            TimeZone VARCHAR(50) DEFAULT 'UTC',
            TimeFormat INT DEFAULT 24,
            DateFormat VARCHAR(3) DEFAULT 'ISO',
            FirstLogin BOOLEAN DEFAULT false,
            GpodderUrl VARCHAR(255) DEFAULT '',
            Pod_Sync_Type VARCHAR(50) DEFAULT 'None',
            GpodderLoginName VARCHAR(255) DEFAULT '',
            GpodderToken VARCHAR(255) DEFAULT '',
            EnableRSSFeeds BOOLEAN DEFAULT FALSE,
            PlaybackSpeed NUMERIC(2,1) DEFAULT 1.0
        )
    """)
    cnx.commit()

    def add_playbackspeed_if_not_exist_users(cursor, cnx):
        try:
            cursor.execute("""
                SELECT column_name
                FROM information_schema.columns
                WHERE table_name='Users'
                AND column_name = 'playbackspeed'
            """)
            existing_column = cursor.fetchone()
            if not existing_column:
                cursor.execute("""
                    ALTER TABLE "Users"
                    ADD COLUMN PlaybackSpeed NUMERIC(2,1) DEFAULT 1.0
                """)
                print("Added 'PlaybackSpeed' column to 'Users' table.")
                cnx.commit()
            else:
                print("Column 'PlaybackSpeed' already exists in 'Users' table.")
        except Exception as e:
            print(f"Error checking PlaybackSpeed column in Users table: {e}")
            # Important: Don't try to commit here, as the transaction is already aborted

    # Usage - should be called during app startup
    add_playbackspeed_if_not_exist_users(cursor, cnx)

    # Create OIDCProviders next
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS "OIDCProviders" (
            ProviderID SERIAL PRIMARY KEY,
            ProviderName VARCHAR(255) NOT NULL,
            ClientID VARCHAR(255) NOT NULL,
            ClientSecret VARCHAR(500) NOT NULL,
            AuthorizationURL VARCHAR(255) NOT NULL,
            TokenURL VARCHAR(255) NOT NULL,
            UserInfoURL VARCHAR(255) NOT NULL,
            Scope VARCHAR(255) DEFAULT 'openid email profile',
            ButtonColor VARCHAR(50) DEFAULT '#000000',
            ButtonText VARCHAR(255) NOT NULL,
            ButtonTextColor VARCHAR(50) DEFAULT '#000000',
            IconSVG TEXT,
            Enabled BOOLEAN DEFAULT true,
            Created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            Modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        )
    """)
    cnx.commit()

    # Now add all columns
    add_column_if_not_exists(cursor, 'Users', 'auth_type', 'VARCHAR(50) DEFAULT \'standard\'')
    add_column_if_not_exists(cursor, 'Users', 'oidc_provider_id', 'INT')
    add_column_if_not_exists(cursor, 'Users', 'oidc_subject', 'VARCHAR(255)')
    cnx.commit()

    # Now add foreign key
    cursor.execute("""
        SELECT COUNT(*)
        FROM information_schema.table_constraints
        WHERE constraint_name = 'fk_oidc_provider'
        AND table_name = 'Users';
    """)
    if cursor.fetchone()[0] == 0:
        cursor.execute("""
            ALTER TABLE "Users"
            ADD CONSTRAINT fk_oidc_provider
            FOREIGN KEY (oidc_provider_id)
            REFERENCES "OIDCProviders"(ProviderID);
        """)
        print("Foreign key constraint 'fk_oidc_provider' added")
    cnx.commit()

    # Create API Keys table last
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS "APIKeys" (
            APIKeyID SERIAL PRIMARY KEY,
            UserID INT,
            APIKey TEXT,
            Created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE
        )
    """)
    cnx.commit()

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS "RssKeys" (
            RssKeyID SERIAL PRIMARY KEY,
            UserID INT,
            RssKey TEXT,
            Created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE
        )   
    """)
    cnx.commit()

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS "RssKeyMap" (
            RssKeyID INT,
            PodcastID INT,
            FOREIGN KEY (RssKeyID) REFERENCES "RssKeys"(RssKeyID) ON DELETE CASCADE
        )
    """)
    cnx.commit()

    cursor.execute("""
        SELECT COUNT(*)
        FROM information_schema.table_constraints
        WHERE constraint_name = 'PlaybackSpeed'
        AND table_name = 'Users';
    """)
    if cursor.fetchone()[0] == 0:
        cursor.execute("""
            ALTER TABLE "Users" ADD COLUMN PlaybackSpeed NUMERIC(2,1) DEFAULT 1.0;
        """)
        print("Column 'PlaybackSpeed' added to Users table")
    cnx.commit()

        # Now add foreign key
    cursor.execute("""
        SELECT COUNT(*)
        FROM information_schema.table_constraints
        WHERE constraint_name = 'PlaybackSpeed'
        AND table_name = 'Podcasts';
    """)
    if cursor.fetchone()[0] == 0:
        cursor.execute("""
            ALTER TABLE "Podcasts" ADD COLUMN PlaybackSpeed NUMERIC(2,1) DEFAULT 1.0;
        """)
        print("Column 'PlaybackSpeed' added to Podcasts table")
    cnx.commit()

    ensure_usernames_lowercase(cnx)


    try:
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "GpodderDevices" (
                DeviceID SERIAL PRIMARY KEY,
                UserID INT NOT NULL,
                DeviceName VARCHAR(255) NOT NULL,
                DeviceType VARCHAR(50) DEFAULT 'desktop',
                DeviceCaption VARCHAR(255),
                IsDefault BOOLEAN DEFAULT FALSE,
                LastSync TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                IsActive BOOLEAN DEFAULT TRUE,
                FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
                UNIQUE(UserID, DeviceName)
            )
        """)
        cnx.commit()
        print("Created GpodderDevices table")

        # Create index for faster lookups
        cursor.execute("""
            CREATE INDEX IF NOT EXISTS idx_gpodder_devices_userid
            ON "GpodderDevices"(UserID)
        """)
        cnx.commit()

        # Create a table for subscription history/sync state
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "GpodderSyncState" (
                SyncStateID SERIAL PRIMARY KEY,
                UserID INT NOT NULL,
                DeviceID INT NOT NULL,
                LastTimestamp BIGINT DEFAULT 0,
                EpisodesTimestamp BIGINT DEFAULT 0,
                FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
                FOREIGN KEY (DeviceID) REFERENCES "GpodderDevices"(DeviceID) ON DELETE CASCADE,
                UNIQUE(UserID, DeviceID)
            )
        """)
        cnx.commit()
        print("Created GpodderSyncState table")
    except Exception as e:
        print(f"Error creating GPodder tables: {e}")

    cursor.execute("""CREATE TABLE IF NOT EXISTS "UserStats" (
                        UserStatsID SERIAL PRIMARY KEY,
                        UserID INT UNIQUE,
                        UserCreated TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                        PodcastsPlayed INT DEFAULT 0,
                        TimeListened INT DEFAULT 0,
                        PodcastsAdded INT DEFAULT 0,
                        EpisodesSaved INT DEFAULT 0,
                        EpisodesDownloaded INT DEFAULT 0,
                        FOREIGN KEY (UserID) REFERENCES "Users"(UserID)
                    )""")


    # Generate a key
    key = Fernet.generate_key()

    # Create the AppSettings table
    cursor.execute("""
        CREATE TABLE IF NOT EXISTS "AppSettings" (
            AppSettingsID SERIAL PRIMARY KEY,
            SelfServiceUser BOOLEAN DEFAULT false,
            DownloadEnabled BOOLEAN DEFAULT true,
            EncryptionKey BYTEA,  -- Set the data type to BYTEA for binary data
            NewsFeedSubscribed BOOLEAN DEFAULT false
        )
    """)

    cursor.execute('SELECT COUNT(*) FROM "AppSettings" WHERE AppSettingsID = 1')
    count = cursor.fetchone()[0]

    if count == 0:
        cursor.execute("""
            INSERT INTO "AppSettings" (SelfServiceUser, DownloadEnabled, EncryptionKey)
            VALUES (false, true, %s)
        """, (key,))

    try:
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "EmailSettings" (
                EmailSettingsID SERIAL PRIMARY KEY,
                Server_Name VARCHAR(255),
                Server_Port INT,
                From_Email VARCHAR(255),
                Send_Mode VARCHAR(255),
                Encryption VARCHAR(255),
                Auth_Required BOOLEAN,
                Username VARCHAR(255),
                Password VARCHAR(255)
            )
        """)
    except Exception as e:
        logging.error(f"Failed to create EmailSettings table: {e}")

    try:
        cursor.execute("""
            SELECT COUNT(*) FROM "EmailSettings"
        """)
        rows = cursor.fetchone()


        if rows[0] == 0:
            cursor.execute("""
                INSERT INTO "EmailSettings" (Server_Name, Server_Port, From_Email, Send_Mode, Encryption, Auth_Required, Username, Password)
                VALUES ('default_server', 587, 'default_email@domain.com', 'default_mode', 'default_encryption', true, 'default_username', 'default_password')
            """)
    except Exception as e:
        print(f"Error setting default email data: {e}")

    def user_exists(cursor, username):
        cursor.execute("""
            SELECT 1 FROM "Users" WHERE Username = %s
        """, (username,))
        return cursor.fetchone() is not None

    # Insert or update the user in the database
    def insert_or_update_user(cursor, hashed_password):
        try:
            if user_exists(cursor, 'guest'):
                cursor.execute("""
                    UPDATE "Users"
                    SET Fullname = %s, Username = %s, Email = %s, Hashed_PW = %s, IsAdmin = %s
                    WHERE Username = %s
                """, ('Background Tasks', 'background_tasks', 'inactive', hashed_password, False, 'guest'))
                logging.info("Updated existing 'guest' user to 'background_tasks' user.")
            elif user_exists(cursor, 'bt'):
                cursor.execute("""
                    UPDATE "Users"
                    SET Fullname = %s, Username = %s, Email = %s, Hashed_PW = %s, IsAdmin = %s
                    WHERE Username = %s
                """, ('Background Tasks', 'background_tasks', 'inactive', hashed_password, False, 'bt'))
                logging.info("Updated existing 'guest' user to 'background_tasks' user.")
            else:
                cursor.execute("""
                    INSERT INTO "Users" (Fullname, Username, Email, Hashed_PW, IsAdmin)
                    VALUES (%s, %s, %s, %s, %s)
                    ON CONFLICT (Username) DO NOTHING
                """, ('Background Tasks', 'background_tasks', 'inactive', hashed_password, False))
        except Exception as e:
            print(f"Error inserting or updating user: {e}")
            logging.error("Error inserting or updating user: %s", e)


    try:
        # Generate and hash the password
        random_password = generate_random_password()
        hashed_password = hash_password(random_password)

        if hashed_password:
            insert_or_update_user(cursor, hashed_password)



    except Exception as e:
        print(f"Error setting default Background Task User: {e}")
        logging.error("Error setting default Background Task User: %s", e)


    try:
        # Check if API key exists for user_id
        cursor.execute('SELECT apikey FROM "APIKeys" WHERE userid = %s', (1,))

        result = cursor.fetchone()

        if result:
            api_key = result[0]
        else:
            import secrets
            import string
            alphabet = string.ascii_letters + string.digits
            api_key = ''.join(secrets.choice(alphabet) for _ in range(64))

            # Insert the new API key into the database using a parameterized query
            cursor.execute('INSERT INTO "APIKeys" (UserID, APIKey) VALUES (%s, %s)', (1, api_key))

            cnx.commit()

        with open("/tmp/web_api_key.txt", "w") as f:
            f.write(api_key)
    except Exception as e:
        print(f"Error creating web key: {e}")

    try:
        # First check if the table exists - use lowercase in the check since lower() is applied
        cursor.execute("""
            SELECT EXISTS (
                SELECT FROM information_schema.tables
                WHERE table_schema = 'public'
                AND lower(table_name) = 'usersettings'
            );
        """)
        table_exists = cursor.fetchone()[0]

        if not table_exists:
            # Fresh install - create the table with all columns
            # Important: Notice we're referencing "Users" (capital U) here
            cursor.execute("""
                CREATE TABLE IF NOT EXISTS "UserSettings" (
                    usersettingid SERIAL PRIMARY KEY,
                    userid INT UNIQUE,
                    theme VARCHAR(255) DEFAULT 'Nordic',
                    startpage VARCHAR(255) DEFAULT 'home',
                    FOREIGN KEY (userid) REFERENCES "Users"(userid)
                )
            """)
            print("UserSettings table created with startpage column included")
        else:
            # Get the actual table name (might be mixed case)
            cursor.execute("""
                SELECT table_name
                FROM information_schema.tables
                WHERE table_schema = 'public'
                AND lower(table_name) = 'usersettings'
            """)
            actual_table_name = cursor.fetchone()[0]
            print(f"Found existing UserSettings table as: {actual_table_name}")

            # Get all column names with their actual case
            cursor.execute(f"""
                SELECT column_name
                FROM information_schema.columns
                WHERE table_schema = 'public'
                AND lower(table_name) = 'usersettings'
            """)
            columns = [col[0] for col in cursor.fetchall()]
            print(f"Existing columns: {columns}")

            # Check if any variation of 'startpage' exists (case-insensitive)
            startpage_column_exists = False
            startpage_column_name = None
            for col in columns:
                if col.lower() == 'startpage':
                    startpage_column_exists = True
                    startpage_column_name = col
                    break

            if not startpage_column_exists:
                # The column doesn't exist, add it
                cursor.execute(f"""
                    ALTER TABLE "{actual_table_name}"
                    ADD COLUMN startpage VARCHAR(255) DEFAULT 'home'
                """)
                print("startpage column added to existing UserSettings table")
            else:
                print(f"startpage column exists as: {startpage_column_name}")

                # IMPORTANT: The API is trying to access 'startpage' but the column is 'StartPage'
                # Check if we need to fix this by checking the error from the log
                cursor.execute(f"""
                    SELECT 1
                    FROM information_schema.columns
                    WHERE table_schema = 'public'
                    AND table_name = lower('{actual_table_name}')
                    AND column_name = 'startpage'
                """)
                lowercase_exists = cursor.fetchone()

                if not lowercase_exists and startpage_column_name != 'startpage':
                    print(f"Column exists as {startpage_column_name} but code tries to access 'startpage'. Adding alias column...")
                    try:
                        # Add a new column and copy data from the existing one
                        cursor.execute(f"""
                            ALTER TABLE "{actual_table_name}"
                            ADD COLUMN startpage VARCHAR(255) DEFAULT 'home'
                        """)
                        # Fixed the column name reference in the UPDATE statement
                        cursor.execute(f"""
                            UPDATE "{actual_table_name}"
                            SET startpage = "{startpage_column_name}"
                        """)
                        print("Added lowercase startpage column for compatibility")
                    except Exception as e:
                        print(f"Error adding compatibility column: {e}")

        # Always commit the transaction
        cnx.commit()
    except Exception as e:
        # Log the general error and rollback
        print(f"Error handling usersettings table: {e}")
        cnx.rollback()

    admin_created = False
    try:
        admin_fullname = os.environ.get("FULLNAME")
        admin_username = os.environ.get("USERNAME")
        admin_email = os.environ.get("EMAIL")
        admin_pw = os.environ.get("PASSWORD")

        if all([admin_fullname, admin_username, admin_email, admin_pw]):
            hashed_pw = hash_password(admin_pw).strip()
            admin_insert_query = """
                INSERT INTO "Users" (Fullname, Username, Email, Hashed_PW, IsAdmin)
                VALUES (%s, %s, %s, %s, %s::boolean)
                ON CONFLICT (Username) DO NOTHING
                RETURNING UserID
            """
            cursor.execute(admin_insert_query, (admin_fullname, admin_username, admin_email, hashed_pw, True))
            admin_created = cursor.fetchone() is not None
            cnx.commit()
    except Exception as e:
        print(f"Error creating default admin: {e}")

    # Now handle UserStats and UserSettings
    try:
        # Background tasks user stats
        cursor.execute("""
            INSERT INTO "UserStats" (UserID) VALUES (1)
            ON CONFLICT (UserID) DO NOTHING
        """)
        if admin_created:
            cursor.execute("""
                INSERT INTO "UserStats" (UserID) VALUES (2)
                ON CONFLICT (UserID) DO NOTHING
            """)
            cursor.execute("""
                INSERT INTO "UserSettings" (UserID, Theme) VALUES (2, 'Nordic')
                ON CONFLICT (UserID) DO NOTHING
            """)
        cnx.commit()
    except Exception as e:
        print(f"Error creating user stats/settings: {e}")

    cursor.execute("""INSERT INTO "UserSettings" (UserID, Theme) VALUES ('1', 'Nordic') ON CONFLICT (UserID) DO NOTHING""")
    if admin_created:
        cursor.execute("""INSERT INTO "UserSettings" (UserID, Theme) VALUES ('2', 'Nordic') ON CONFLICT (UserID) DO NOTHING""")


    try:
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "Podcasts" (
                PodcastID SERIAL PRIMARY KEY,
                PodcastIndexID INT,
                PodcastName TEXT,
                ArtworkURL TEXT,
                Author TEXT,
                Categories TEXT,
                Description TEXT,
                EpisodeCount INT,
                FeedURL TEXT,
                WebsiteURL TEXT,
                Explicit BOOLEAN,
                UserID INT,
                AutoDownload BOOLEAN DEFAULT FALSE,
                StartSkip INT DEFAULT 0,
                EndSkip INT DEFAULT 0,
                Username TEXT,
                Password TEXT,
                IsYouTubeChannel BOOLEAN DEFAULT FALSE,
                NotificationsEnabled BOOLEAN DEFAULT FALSE,
                FeedCutoffDays INT DEFAULT 0,
                PlaybackSpeed NUMERIC(2,1) DEFAULT 1.0,
                PlaybackSpeedCustomized BOOLEAN DEFAULT FALSE,
                FOREIGN KEY (UserID) REFERENCES "Users"(UserID)
            )
        """)
        cnx.commit()  # Ensure changes are committed
    except Exception as e:
        print(f"Error adding Podcasts table: {e}")

    try:
        # Add unique constraint on UserID and FeedURL to fix the ON CONFLICT error
        cursor.execute("""
            DO $$
            BEGIN
                IF NOT EXISTS (
                    SELECT 1
                    FROM pg_constraint
                    WHERE conname = 'podcasts_userid_feedurl_key'
                ) THEN
                    -- Add the constraint if it doesn't exist
                    ALTER TABLE "Podcasts"
                    ADD CONSTRAINT podcasts_userid_feedurl_key
                    UNIQUE (UserID, FeedURL);
                END IF;
            END
            $$;
        """)
        cnx.commit()
        print("Added unique constraint on UserID and FeedURL to Podcasts table")
    except Exception as e:
        print(f"Error adding unique constraint to Podcasts table: {e}")

    def add_youtube_column_if_not_exist(cursor, cnx):
        try:
            cursor.execute("""
                SELECT column_name
                FROM information_schema.columns
                WHERE table_name='Podcasts'
                AND column_name = 'isyoutubechannel'
            """)
            existing_column = cursor.fetchone()

            if not existing_column:
                cursor.execute("""
                    ALTER TABLE "Podcasts"
                    ADD COLUMN "isyoutubechannel" BOOLEAN DEFAULT FALSE
                """)
                print("Added 'IsYouTubeChannel' column to 'Podcasts' table.")
                cnx.commit()
            else:
                print('IsYouTubeChannel column already exists')
        except Exception as e:
            print(f"Error adding IsYouTubeChannel column to Podcasts table: {e}")

    # Usage
    add_youtube_column_if_not_exist(cursor, cnx)

    def add_playbackspeed_if_not_exist_podcasts(cursor, cnx):
        try:
            cursor.execute("""
                SELECT column_name
                FROM information_schema.columns
                WHERE table_name='Podcasts'
                AND column_name = 'playbackspeed'
            """)
            existing_column = cursor.fetchone()
            if not existing_column:
                cursor.execute("""
                    ALTER TABLE "Podcasts"
                    ADD COLUMN PlaybackSpeed NUMERIC(2,1) DEFAULT 1.0
                """)
                print("Added 'PlaybackSpeed' column to 'Podcasts' table.")
                cnx.commit()
            else:
                print("Column 'PlaybackSpeed' already exists in 'Podcasts' table. ")
        except Exception as e:
            print(f"Error checking PlaybackSpeed column in Podcasts table: {e}")
            # No commit or rollback here, just like in your working example

    # Usage - should be called during app startup
    add_playbackspeed_if_not_exist_podcasts(cursor, cnx)

    def add_playbackspeed_customized_if_not_exist(cursor, cnx):
        try:
            cursor.execute("""
                SELECT column_name
                FROM information_schema.columns
                WHERE table_name='Podcasts'
                AND column_name = 'playbackspeedcustomized'
            """)
            existing_column = cursor.fetchone()
            if not existing_column:
                cursor.execute("""
                    ALTER TABLE "Podcasts"
                    ADD COLUMN PlaybackSpeedCustomized BOOLEAN DEFAULT FALSE
                """)
                print("Added 'PlaybackSpeedCustomized' column to 'Podcasts' table.")
                cnx.commit()
            else:
                print('PlaybackSpeedCustomized column already exists')
        except Exception as e:
            print(f"Error adding PlaybackSpeedCustomized column to Podcasts table: {e}")

    # Usage - should be called during app startup
    add_playbackspeed_customized_if_not_exist(cursor, cnx)

    def add_feed_cutoff_column_if_not_exist(cursor, cnx):
        try:
            cursor.execute("""
                SELECT column_name
                FROM information_schema.columns
                WHERE table_name='Podcasts'
                AND column_name = 'feedcutoffdays'
            """)
            existing_column = cursor.fetchone()

            if not existing_column:
                cursor.execute("""
                    ALTER TABLE "Podcasts"
                    ADD COLUMN "feedcutoffdays" INT DEFAULT 0
                """)
                print("Added 'feedcutoffdays' column to 'Podcasts' table.")
                cnx.commit()
        except Exception as e:
            print(f"Error adding feedcutoffdays column to Podcasts table: {e}")

    add_feed_cutoff_column_if_not_exist(cursor, cnx)

    cursor.execute("SELECT to_regclass('public.\"Podcasts\"')")

    try:
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "Episodes" (
                EpisodeID SERIAL PRIMARY KEY,
                PodcastID INT,
                EpisodeTitle TEXT,
                EpisodeDescription TEXT,
                EpisodeURL TEXT,
                EpisodeArtwork TEXT,
                EpisodePubDate TIMESTAMP,
                EpisodeDuration INT,
                Completed BOOLEAN DEFAULT FALSE,
                FOREIGN KEY (PodcastID) REFERENCES "Podcasts"(PodcastID)
            )
        """)

        cnx.commit()  # Ensure changes are committed
    except Exception as e:
        print(f"Error adding Episodes table: {e}")

    try:
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "YouTubeVideos" (
                VideoID SERIAL PRIMARY KEY,
                PodcastID INT,
                VideoTitle TEXT,
                VideoDescription TEXT,
                VideoURL TEXT,
                ThumbnailURL TEXT,
                PublishedAt TIMESTAMP,
                Duration INT,
                YouTubeVideoID TEXT,
                Completed BOOLEAN DEFAULT FALSE,
                ListenPosition INT DEFAULT 0,
                FOREIGN KEY (PodcastID) REFERENCES "Podcasts"(PodcastID)
            )
        """)

        cnx.commit()  # Ensure changes are committed
    except Exception as e:
        print(f"Error adding YoutubeVideos table: {e}")

    def create_index_if_not_exists(cursor, index_name, table_name, column_name):
        cursor.execute(f"""
            SELECT 1
            FROM pg_indexes
            WHERE lower(indexname) = lower('{index_name}') AND tablename = '{table_name}'
        """)
        if not cursor.fetchone():
            cursor.execute(f'CREATE INDEX {index_name} ON "{table_name}"({column_name})')

    create_index_if_not_exists(cursor, "idx_podcasts_userid", "Podcasts", "UserID")
    create_index_if_not_exists(cursor, "idx_episodes_podcastid", "Episodes", "PodcastID")
    create_index_if_not_exists(cursor, "idx_episodes_episodepubdate", "Episodes", "EpisodePubDate")

    try:
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "People" (
                PersonID SERIAL PRIMARY KEY,
                Name TEXT,
                PersonImg TEXT,
                PeopleDBID INT,
                AssociatedPodcasts TEXT,
                UserID INT,
                FOREIGN KEY (UserID) REFERENCES "Users"(UserID)
            );

        """)
        cnx.commit()
    except Exception as e:
        print(f"Error creating People table: {e}")

    try:
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "PeopleEpisodes" (
                EpisodeID SERIAL PRIMARY KEY,
                PersonID INT,
                PodcastID INT,
                EpisodeTitle TEXT,
                EpisodeDescription TEXT,
                EpisodeURL TEXT,
                EpisodeArtwork TEXT,
                EpisodePubDate TIMESTAMP,
                EpisodeDuration INT,
                AddedDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (PersonID) REFERENCES "People"(PersonID),
                FOREIGN KEY (PodcastID) REFERENCES "Podcasts"(PodcastID)
            );

        """)
        cnx.commit()
    except Exception as e:
        print(f"Error creating People table: {e}")

    create_index_if_not_exists(cursor, "idx_people_episodes_person", "PeopleEpisodes", "PersonID")
    create_index_if_not_exists(cursor, "idx_people_episodes_podcast", "PeopleEpisodes", "PodcastID")


    try:
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "SharedEpisodes" (
                SharedEpisodeID SERIAL PRIMARY KEY,
                EpisodeID INT,
                UrlKey TEXT,
                ExpirationDate TIMESTAMP,
                FOREIGN KEY (EpisodeID) REFERENCES "Episodes"(EpisodeID)
            )
        """)
        cnx.commit()
    except Exception as e:
        print(f"Error creating SharedEpisodes table: {e}")


    cursor.execute("""CREATE TABLE IF NOT EXISTS "UserEpisodeHistory" (
                        UserEpisodeHistoryID SERIAL PRIMARY KEY,
                        UserID INT,
                        EpisodeID INT,
                        ListenDate TIMESTAMP,
                        ListenDuration INT,
                        FOREIGN KEY (UserID) REFERENCES "Users"(UserID),
                        FOREIGN KEY (EpisodeID) REFERENCES "Episodes"(EpisodeID)
                    )""")

    cursor.execute("""
        CREATE TABLE IF NOT EXISTS "UserVideoHistory" (
            UserVideoHistoryID SERIAL PRIMARY KEY,
            UserID INT,
            VideoID INT,
            ListenDate TIMESTAMP,
            ListenDuration INT DEFAULT 0,
            FOREIGN KEY (UserID) REFERENCES "Users"(UserID),
            FOREIGN KEY (VideoID) REFERENCES "YouTubeVideos"(VideoID)
        )
    """)

    def add_history_constraints_if_not_exists(cursor, cnx):
        try:
            # Check/add constraint for UserEpisodeHistory
            cursor.execute("""
                SELECT constraint_name
                FROM information_schema.table_constraints
                WHERE table_name = 'UserEpisodeHistory'
                AND constraint_type = 'UNIQUE'
                AND constraint_name = 'user_episode_unique'
            """)

            if not cursor.fetchone():
                cursor.execute("""
                    ALTER TABLE "UserEpisodeHistory"
                    ADD CONSTRAINT user_episode_unique
                    UNIQUE (UserID, EpisodeID)
                """)
                print("Added unique constraint to UserEpisodeHistory table.")
                cnx.commit()

            # Check/add constraint for UserVideoHistory
            cursor.execute("""
                SELECT constraint_name
                FROM information_schema.table_constraints
                WHERE table_name = 'UserVideoHistory'
                AND constraint_type = 'UNIQUE'
                AND constraint_name = 'user_video_unique'
            """)

            if not cursor.fetchone():
                cursor.execute("""
                    ALTER TABLE "UserVideoHistory"
                    ADD CONSTRAINT user_video_unique
                    UNIQUE (UserID, VideoID)
                """)
                print("Added unique constraint to UserVideoHistory table.")
                cnx.commit()
        except Exception as e:
            print(f"Error adding unique constraints to history tables: {e}")

    # Call this after creating both history tables
    add_history_constraints_if_not_exists(cursor, cnx)

    cursor.execute("""CREATE TABLE IF NOT EXISTS "SavedEpisodes" (
                        SaveID SERIAL PRIMARY KEY,
                        UserID INT,
                        EpisodeID INT,
                        SaveDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                        FOREIGN KEY (UserID) REFERENCES "Users"(UserID),
                        FOREIGN KEY (EpisodeID) REFERENCES "Episodes"(EpisodeID)
                    )""")

    cursor.execute("""CREATE TABLE IF NOT EXISTS "SavedVideos" (
        SaveID SERIAL PRIMARY KEY,
        UserID INT,
        VideoID INT,
        SaveDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        FOREIGN KEY (UserID) REFERENCES "Users"(UserID),
        FOREIGN KEY (VideoID) REFERENCES "YouTubeVideos"(VideoID)
        )""")


    # Create the DownloadedEpisodes table
    cursor.execute("""CREATE TABLE IF NOT EXISTS "DownloadedEpisodes" (
                    DownloadID SERIAL PRIMARY KEY,
                    UserID INT,
                    EpisodeID INT,
                    DownloadedDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                    DownloadedSize INT,
                    DownloadedLocation VARCHAR(255),
                    FOREIGN KEY (UserID) REFERENCES "Users"(UserID),
                    FOREIGN KEY (EpisodeID) REFERENCES "Episodes"(EpisodeID)
                    )""")

    cursor.execute("""CREATE TABLE IF NOT EXISTS "DownloadedVideos" (
        DownloadID SERIAL PRIMARY KEY,
        UserID INT,
        VideoID INT,
        DownloadedDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        DownloadedSize INT,
        DownloadedLocation VARCHAR(255),
        FOREIGN KEY (UserID) REFERENCES "Users"(UserID),
        FOREIGN KEY (VideoID) REFERENCES "YouTubeVideos"(VideoID)
        )""")

    # Create the EpisodeQueue table
    cursor.execute("""CREATE TABLE IF NOT EXISTS "EpisodeQueue" (
        QueueID SERIAL PRIMARY KEY,
        QueueDate TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
        UserID INT,
        EpisodeID INT,
        QueuePosition INT NOT NULL DEFAULT 0,
        is_youtube BOOLEAN DEFAULT FALSE,
        FOREIGN KEY (UserID) REFERENCES "Users"(UserID)
    )""")

    def remove_episode_queue_constraint(cursor, cnx):
        try:
            # First check if the constraint exists
            check_constraint_query = """
                SELECT constraint_name
                FROM information_schema.table_constraints
                WHERE table_name = 'EpisodeQueue'
                AND constraint_name = 'EpisodeQueue_episodeid_fkey'
                AND constraint_type = 'FOREIGN KEY'
            """
            cursor.execute(check_constraint_query)
            constraint = cursor.fetchone()

            if constraint:
                # If it exists, drop it
                cursor.execute('ALTER TABLE "EpisodeQueue" DROP CONSTRAINT "EpisodeQueue_episodeid_fkey"')
                cnx.commit()
                print("Removed EpisodeQueue foreign key constraint")
            else:
                print("EpisodeQueue foreign key constraint not found - no action needed")

        except Exception as e:
            print(f"Error managing EpisodeQueue constraint: {e}")
            cnx.rollback()

    remove_episode_queue_constraint(cursor, cnx)

    def add_queue_youtube_column_if_not_exist(cursor, cnx):
        try:
            # Check if column exists using PostgreSQL's system catalog
            cursor.execute("""
                SELECT EXISTS (
                    SELECT 1
                    FROM information_schema.columns
                    WHERE table_name = 'EpisodeQueue'
                    AND column_name = 'is_youtube'
                )
            """)
            column_exists = cursor.fetchone()[0]

            if not column_exists:
                cursor.execute("""
                    ALTER TABLE "EpisodeQueue"
                    ADD COLUMN is_youtube BOOLEAN DEFAULT FALSE
                """)
                cnx.commit()
                print("Added 'is_youtube' column to 'EpisodeQueue' table.")
            else:
                print("Column 'is_youtube' already exists in 'EpisodeQueue' table.")

        except Exception as e:
            cnx.rollback()
            print(f"Error managing is_youtube column: {e}")

    add_queue_youtube_column_if_not_exist(cursor, cnx)

    # Create the Sessions table
    cursor.execute("""CREATE TABLE IF NOT EXISTS "Sessions" (
                    SessionID SERIAL PRIMARY KEY,
                    UserID INT,
                    value TEXT,
                    expire TIMESTAMP NOT NULL,
                    FOREIGN KEY (UserID) REFERENCES "Users"(UserID)
                    )""")
    cnx.commit()

    # First let's define our functions to check and add columns/tables
    def add_notification_column_if_not_exists(cursor, cnx):
        try:
            cursor.execute("""
                SELECT EXISTS (
                    SELECT 1
                    FROM information_schema.columns
                    WHERE table_name = 'Podcasts'
                    AND column_name = 'notificationsenabled'
                )
            """)
            column_exists = cursor.fetchone()[0]

            if not column_exists:
                cursor.execute("""
                    ALTER TABLE "Podcasts"
                    ADD COLUMN NotificationsEnabled BOOLEAN DEFAULT FALSE
                """)
                cnx.commit()
                print("Added 'NotificationsEnabled' column to 'Podcasts' table.")
            else:
                print("Column 'NotificationsEnabled' already exists in 'Podcasts' table.")
        except Exception as e:
            cnx.rollback()
            print(f"Error managing NotificationsEnabled column: {e}")

    add_notification_column_if_not_exists(cursor, cnx)

    # Now create the notification settings table if it doesn't exist
    try:
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "UserNotificationSettings" (
                SettingID SERIAL PRIMARY KEY,
                UserID INT,
                Platform VARCHAR(50) NOT NULL,
                Enabled BOOLEAN DEFAULT TRUE,
                NtfyTopic VARCHAR(255),
                NtfyServerUrl VARCHAR(255) DEFAULT 'https://ntfy.sh',
                GotifyUrl VARCHAR(255),
                GotifyToken VARCHAR(255),
                created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (UserID) REFERENCES "Users"(UserID),
                UNIQUE(UserID, platform)
            )
        """)
        cnx.commit()
        print("Checked/Created UserNotificationSettings table")
    except Exception as e:
        print(f"Error creating UserNotificationSettings table: {e}")


    try:
        # Create Playlists table with the unique constraint
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "Playlists" (
                PlaylistID SERIAL PRIMARY KEY,
                UserID INT NOT NULL,
                Name VARCHAR(255) NOT NULL,
                Description TEXT,
                IsSystemPlaylist BOOLEAN NOT NULL DEFAULT FALSE,
                PodcastIDs INTEGER[], -- Can be NULL to mean "all podcasts"
                IncludeUnplayed BOOLEAN NOT NULL DEFAULT TRUE,
                IncludePartiallyPlayed BOOLEAN NOT NULL DEFAULT TRUE,
                IncludePlayed BOOLEAN NOT NULL DEFAULT FALSE,
                MinDuration INTEGER, -- NULL means no minimum
                MaxDuration INTEGER, -- NULL means no maximum
                SortOrder VARCHAR(50) NOT NULL DEFAULT 'date_desc'
                    CHECK (SortOrder IN ('date_asc', 'date_desc',
                                       'duration_asc', 'duration_desc',
                                       'listen_progress', 'completion')),
                GroupByPodcast BOOLEAN NOT NULL DEFAULT FALSE,
                MaxEpisodes INTEGER, -- NULL means no limit
                PlayProgressMin FLOAT, -- NULL means no minimum progress requirement
                PlayProgressMax FLOAT, -- NULL means no maximum progress limit
                TimeFilterHours INTEGER, -- NULL means no time filter
                LastUpdated TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                Created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
                IconName VARCHAR(50) NOT NULL DEFAULT 'ph-playlist',
                FOREIGN KEY (UserID) REFERENCES "Users"(UserID) ON DELETE CASCADE,
                UNIQUE(UserID, Name),
                CHECK (PlayProgressMin IS NULL OR (PlayProgressMin >= 0 AND PlayProgressMin <= 100)),
                CHECK (PlayProgressMax IS NULL OR (PlayProgressMax >= 0 AND PlayProgressMax <= 100)),
                CHECK (PlayProgressMin IS NULL OR PlayProgressMax IS NULL OR PlayProgressMin <= PlayProgressMax),
                CHECK (MinDuration IS NULL OR MinDuration >= 0),
                CHECK (MaxDuration IS NULL OR MaxDuration >= 0),
                CHECK (MinDuration IS NULL OR MaxDuration IS NULL OR MinDuration <= MaxDuration),
                CHECK (TimeFilterHours IS NULL OR TimeFilterHours > 0),
                CHECK (MaxEpisodes IS NULL OR MaxEpisodes > 0)
            )
        """)
        cnx.commit()

        # First add the unique constraint if it doesn't exist
        cursor.execute("""
            DO $$
            BEGIN
                IF NOT EXISTS (
                    SELECT 1
                    FROM pg_constraint
                    WHERE conname = 'playlists_userid_name_key'
                ) THEN
                    ALTER TABLE "Playlists"
                    ADD CONSTRAINT playlists_userid_name_key UNIQUE(UserID, Name);
                END IF;
            END $$;
        """)
        cnx.commit()

        # Create PlaylistContents table
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS "PlaylistContents" (
                PlaylistContentID SERIAL PRIMARY KEY,
                PlaylistID INT,
                EpisodeID INT,
                VideoID INT,
                Position INT,
                DateAdded TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                FOREIGN KEY (PlaylistID) REFERENCES "Playlists"(PlaylistID) ON DELETE CASCADE,
                FOREIGN KEY (EpisodeID) REFERENCES "Episodes"(EpisodeID) ON DELETE CASCADE,
                FOREIGN KEY (VideoID) REFERENCES "YouTubeVideos"(VideoID) ON DELETE CASCADE,
                CHECK ((EpisodeID IS NOT NULL AND VideoID IS NULL) OR (EpisodeID IS NULL AND VideoID IS NOT NULL))
            )
        """)
        cnx.commit()

        # Create indexes
        cursor.execute("""
            CREATE INDEX IF NOT EXISTS idx_playlists_userid ON "Playlists"(UserID);
            CREATE INDEX IF NOT EXISTS idx_playlist_contents_playlistid ON "PlaylistContents"(PlaylistID);
            CREATE INDEX IF NOT EXISTS idx_playlist_contents_episodeid ON "PlaylistContents"(EpisodeID);
            CREATE INDEX IF NOT EXISTS idx_playlist_contents_videoid ON "PlaylistContents"(VideoID);
        """)
        cnx.commit()

        # Define system playlists
        system_playlists = [
            {
                'name': 'Quick Listens',
                'description': 'Short episodes under 15 minutes, perfect for quick breaks',
                'min_duration': None,
                'max_duration': 900,  # 15 minutes
                'sort_order': 'duration_asc',
                'icon_name': 'ph-fast-forward'
            },
            {
                'name': 'Longform',
                'description': 'Extended episodes over 1 hour, ideal for long drives or deep dives',
                'min_duration': 3600,  # 1 hour
                'max_duration': None,
                'sort_order': 'duration_desc',
                'icon_name': 'ph-car'
            },
            {
                'name': 'Currently Listening',
                'description': 'Episodes you\'ve started but haven\'t finished',
                'min_duration': None,
                'max_duration': None,
                'sort_order': 'date_desc',
                'include_unplayed': False,
                'include_partially_played': True,
                'include_played': False,
                'icon_name': 'ph-play'
            },
            {
                'name': 'Fresh Releases',
                'description': 'Latest episodes from the last 24 hours',
                'min_duration': None,
                'max_duration': None,
                'sort_order': 'date_desc',
                'include_unplayed': True,
                'include_partially_played': False,
                'include_played': False,
                'time_filter_hours': 24,
                'icon_name': 'ph-sparkle'
            },
            {
                'name': 'Weekend Marathon',
                'description': 'Longer episodes (30+ minutes) perfect for weekend listening',
                'min_duration': 1800,  # 30 minutes
                'max_duration': None,
                'sort_order': 'duration_desc',
                'group_by_podcast': True,
                'icon_name': 'ph-couch'
            },
            {
                'name': 'Commuter Mix',
                'description': 'Episodes between 20-40 minutes, ideal for average commute times',
                'min_duration': 1200,  # 20 minutes
                'max_duration': 2400,  # 40 minutes
                'sort_order': 'date_desc',
                'icon_name': 'ph-train'
            },
            {
                'name': 'Almost Done',
                'description': 'Episodes you\'re close to finishing (75%+ complete)',
                'min_duration': None,
                'max_duration': None,
                'sort_order': 'date_asc',
                'include_unplayed': False,
                'include_partially_played': True,
                'include_played': False,
                'play_progress_min': 75.0,  # Add this
                'play_progress_max': None,  # Can add this too
                'icon_name': 'ph-hourglass'
            }
        ]

        # Insert system playlists
        for playlist in system_playlists:
            try:
                # First check if this playlist already exists
                cursor.execute("""
                    SELECT COUNT(*)
                    FROM "Playlists"
                    WHERE UserID = 1 AND Name = %s AND IsSystemPlaylist = TRUE
                """, (playlist['name'],))

                if cursor.fetchone()[0] == 0:
                    cursor.execute("""
                        INSERT INTO "Playlists" (
                            UserID,
                            Name,
                            Description,
                            IsSystemPlaylist,
                            MinDuration,
                            MaxDuration,
                            SortOrder,
                            GroupByPodcast,
                            IncludeUnplayed,
                            IncludePartiallyPlayed,
                            IncludePlayed,
                            IconName,
                            TimeFilterHours,
                            PlayProgressMin,
                            PlayProgressMax
                        ) VALUES (
                            1,
                            %s,
                            %s,
                            TRUE,
                            %s,
                            %s,
                            %s,
                            %s,
                            %s,
                            %s,
                            %s,
                            %s,
                            %s,
                            %s,
                            %s
                        )
                    """, (
                        playlist['name'],
                        playlist['description'],
                        playlist.get('min_duration'),
                        playlist.get('max_duration'),
                        playlist.get('sort_order', 'date_asc'),
                        playlist.get('group_by_podcast', False),
                        playlist.get('include_unplayed', True),
                        playlist.get('include_partially_played', True),
                        playlist.get('include_played', False),
                        playlist.get('icon_name', 'ph-playlist'),
                        playlist.get('time_filter_hours'),
                        playlist.get('play_progress_min'),
                        playlist.get('play_progress_max')
                    ))
                    cnx.commit()
                    print(f"Successfully added system playlist: {playlist['name']}")
                else:
                    print(f"System playlist already exists: {playlist['name']}")

            except Exception as e:
                print(f"Error handling system playlist {playlist['name']}: {e}")
                continue

        print("Checked/Created Playlist Tables")

    except psycopg.Error as err:
        logging.error(f"Database error: {err}")
    except Exception as e:
        logging.error(f"General error: {e}")


except psycopg.Error as err:
    logging.error(f"Database error: {err}")
except Exception as e:
    logging.error(f"General error: {e}")

# Ensure to close the cursor and connection
finally:
    if 'cursor' in locals():
        cursor.close()
    if 'cnx' in locals():
        cnx.close()
