#!/usr/bin/env python -O
# -*- coding: utf-8 -*-

import os
import unittest
import platform
import traceback
import sys
from pathlib import Path

# Add the mariadb source module to the path BEFORE importing
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))

import mariadb
# Import constants as module attribute, not submodule
STATUS = mariadb.constants.STATUS
from packaging.version import parse as parse_version
from packaging import version

from ..base_test import create_connection, is_skysql, is_maxscale, is_native, get_host_suffix
from ..conftest import get_test_config as conf


class TestConnection(unittest.TestCase):

    def setUp(self):
        self.connection = create_connection()

    def tearDown(self):
        self.connection.close()


    def test_conpy36(self):
        if platform.system() == "Windows":
            self.skipTest("unix_socket not supported on Windows")
        default_conf = conf()
        try:
            mariadb.connect(user=default_conf["user"],
                              unix_socket="/does_not_exist/x.sock",
                              port=default_conf["port"],
                              host=default_conf["host"])
        except (mariadb.OperationalError,):
            # make asan happy
            tb = sys.exc_info()[2]
            traceback.clear_frames(tb)
            pass

    def test_connection_default_file(self):
        if is_native():
            self.skipTest("default file not supported on native yet")
        if os.path.exists("client.cnf"):
            os.remove("client.cnf")
        default_conf = conf()
        f = open("client.cnf", "w+")
        f.write("[client]\n")
        f.write("host =%s\n" % default_conf["host"])
        f.write("port =%i\n" % default_conf["port"])
        f.write("user =%s\n" % default_conf["user"])
        if "password" in default_conf:
            f.write("password =%s\n" % default_conf["password"])
        f.write("database =%s\n" % default_conf["database"])
        f.close()

        new_conn = mariadb.connect(user=default_conf["user"], ssl=True,
                                     default_file="./client.cnf")
        self.assertEqual(new_conn.database, default_conf["database"])
        del new_conn
        os.remove("client.cnf")

    def test_autocommit(self):
        conn = self.connection
        conn.autocommit = False
        self.assertEqual(conn.autocommit, False)
        # revert
        conn.autocommit = True
        self.assertEqual(conn.autocommit, True)

    def test_local_infile(self):
        default_conf = conf()
        new_conn = mariadb.connect(**default_conf, local_infile=False)
        cursor = new_conn.cursor()
        cursor.execute("CREATE TEMPORARY TABLE t1 (a int)")
        try:
            cursor.execute("LOAD DATA LOCAL INFILE 'x.x' INTO TABLE t1")
        except (mariadb.OperationalError, mariadb.DatabaseError):
            # make asan happy
            if mariadb._have_asan:
                tb = sys.exc_info()[2]
                traceback.clear_frames(tb)
            pass
        del cursor
        del new_conn

    def test_tls_version(self):
        if is_maxscale():
            self.skipTest("MAXSCALE test has no SSL on port by default")
        default_conf = conf()
        conn = mariadb.connect(**default_conf, tls_version="TLSv1.2")
        cursor = conn.cursor()
        cursor.execute("SHOW STATUS LIKE 'ssl_version'")
        row = cursor.fetchone()
        self.assertEqual(row[1], "TLSv1.2")
        cursor.close()
        conn.close()

    def test_init_command(self):
        default_conf = conf()
        new_conn = mariadb.connect(**default_conf, init_command="SET @a:=1")
        cursor = new_conn.cursor()
        cursor.execute("SELECT @a")
        row = cursor.fetchone()
        self.assertEqual(row[0], 1)
        del cursor
        del new_conn

    def test_compress(self):
        default_conf = conf()
        new_conn = mariadb.connect(**default_conf, compress=True)
        cursor = new_conn.cursor()
        cursor.execute("SHOW SESSION STATUS LIKE 'compression'")
        row = cursor.fetchone()
        if is_maxscale() or is_native():
            self.assertEqual(row[1], "OFF")
        else:
            self.assertEqual(row[1], "ON")
        del cursor
        del new_conn

    def test_schema(self):
        if self.connection.server_version < 100202:
            self.skipTest("session tracking not supported")
        if is_maxscale():
            self.skipTest("MAXSCALE doesn't tell schema change for now")

        default_conf = conf()
        conn = create_connection()
        self.assertEqual(conn.database, default_conf["database"])
        cursor = conn.cursor()
        cursor.execute("DROP SCHEMA IF EXISTS test1")
        cursor.execute("CREATE SCHEMA test1")
        cursor.execute("USE test1")
        self.assertEqual(conn.database, "test1")
        conn.database = default_conf["database"]
        self.assertEqual(conn.database, default_conf["database"])
        with self.assertRaises(mariadb.Error):
            conn.database = "nonExisting"
        cursor.close()
        conn.close()

    def test_ping(self):
        if is_maxscale():
            self.skipTest("MAXSCALE wrong thread id")
        with create_connection() as conn:
            with conn.cursor() as cursor:
                oldid = conn.connection_id

                try:
                    cursor.execute("KILL {id}" . format(id=oldid))
                except (mariadb.Error, mariadb.OperationalError):
                    pass
            try:
                conn.ping()
            except (mariadb.InterfaceError, mariadb.DatabaseError):
                pass           

    def test_dump_debug_info(self):
        with create_connection() as conn:
            with conn.cursor() as cursor:
                cursor.execute(
                    "SELECT COUNT(*) FROM information_schema.USER_PRIVILEGES "
                    "WHERE GRANTEE = CONCAT(\"'\", SUBSTRING_INDEX(CURRENT_USER(),'@',1), \"'@'\", SUBSTRING_INDEX(CURRENT_USER(),'@',-1), \"'\") "
                    "AND PRIVILEGE_TYPE = 'SUPER'"
                )
                row = cursor.fetchone()
                has_super = row is not None and row[0] > 0
            if not has_super:
                self.skipTest("SUPER privilege required for dump_debug_info")
            conn.dump_debug_info()

    def test_open(self):
        """Test connection.open property"""
        if is_maxscale():
            self.skipTest("MAXSCALE wrong thread id")
        
        # Test with active connection
        conn = create_connection()
        try:
            # Connection should be open
            self.assertTrue(conn.open)
            
            # Kill the connection
            oldid = conn.connection_id
            with conn.cursor() as cursor:
                try:
                    cursor.execute("KILL {id}".format(id=oldid))
                except (mariadb.Error, mariadb.OperationalError):
                    pass
            
            # Connection should now be closed/not open
            self.assertFalse(conn.open)
        finally:
            try:
                conn.close()
            except:
                pass

    def test_ed25519(self):
        if is_native():
            self.skipTest("Ed25519 not supported on native")
        if is_skysql():
            self.skipTest("Test fail on SkySQL")
        default_conf = conf()
        if is_maxscale():
            self.skipTest("MAXSCALE doesn't support ed25519 for now")
        if self.connection.server_version < 100122:
            self.skipTest("ed25519 not supported")

        conn = create_connection()
        curs = conn.cursor(buffered=True)

        if self.connection.server_name == "localhost":
            curs.execute("select * from information_schema.plugins where "
                         "plugin_name ='unix_socket' and "
                         "plugin_status ='ACTIVE'")
            if curs.rowcount > 0:
                del curs
                self.skipTest("unix_socket is active")

        cursor = conn.cursor()
        try:
            cursor.execute("INSTALL SONAME 'auth_ed25519'")
        except (mariadb.DatabaseError, mariadb.OperationalError):
            self.skipTest("Server couldn't load auth_ed25519")
        cursor.execute("DROP USER IF EXISTS eduser")
        if self.connection.server_version < 100400:
            cursor.execute("CREATE USER eduser"+get_host_suffix()+" IDENTIFIED VIA ed25519 "
                           "USING "
                           "'6aW9C7ENlasUfymtfMvMZZtnkCVlcb1ssxOLJ0kj/AA'")
        else:
            cursor.execute("CREATE USER eduser"+get_host_suffix()+" IDENTIFIED VIA ed25519 "
                           "USING PASSWORD('MySup8%rPassw@ord')")
        cursor.execute("GRANT ALL on " + default_conf["database"] +
                       ".* to eduser"+get_host_suffix())
        conn2 = create_connection({"user": "eduser",
                                   "password": "MySup8%rPassw@ord"})
        # disabling this test part for now
        # try:
        #     create_connection({"user": "eduser",
        #                        "password": "MySup8%rPassw@ord",
        #                        "plugin_dir": "wrong_plugin_dir"})
        #     self.fail("wrong plugin directory, must not have found "
        #               "authentication plugin")
        # except (mariadb.OperationalError):
        #     pass
        cursor.execute("DROP USER IF EXISTS eduser"+get_host_suffix())
        del cursor, conn2, conn

    def test_conpy46(self):
        with create_connection() as con:
            with con.cursor() as cursor:
                cursor.execute("SELECT 'foo'")
                row = cursor.fetchone()
                self.assertEqual(row[0], "foo")
            try:
                cursor.execute("SELECT 'bar'")
            except mariadb.ProgrammingError:
                pass
        try:
            cursor = con.cursor()
        except mariadb.ProgrammingError:
            pass

    def test_conpy101(self):
        default_conf = conf()
        c1 = mariadb.connect(**default_conf)
        self.assertEqual(c1.autocommit, False)
        c1.close()
        c1 = mariadb.connect(**default_conf, autocommit=True)
        self.assertEqual(c1.autocommit, True)
        c1.close()

    def test_db_attribute(self):
        with create_connection() as con:
             with con.cursor() as cursor:
                 cursor.execute("drop schema if exists test123")
                 db = con.database
                 try:
                     cursor.execute("create schema test123")
                 except mariadb.Error:
                     pass
                 con.database = "test123"
                 cursor.execute("select database()", buffered=True)
                 row = cursor.fetchone()
                 self.assertEqual(row[0], "test123")
                 con.database = db
                 cursor.execute("select database()", buffered=True)
                 row = cursor.fetchone()
                 self.assertEqual(row[0], db)
                 self.assertEqual(row[0], con.database)
                 cursor.execute("drop schema test123")

    def test_server_status(self):
        con = create_connection()
        try:
            self.assertTrue(not con.server_status & STATUS.AUTOCOMMIT)
            con.autocommit = True
            self.assertTrue(con.server_status & STATUS.AUTOCOMMIT)
            con.autocommit = False
            self.assertTrue(not con.server_status & STATUS.AUTOCOMMIT)
        finally:
            con.close()

    def test_conpy175(self):
        default_conf = conf()
        conn = mariadb.connect(**default_conf)
        str = "Bob's"
        cursor= conn.cursor()
        cursor.execute("SET session sql_mode='NO_BACKSLASH_ESCAPES'")
        newstr = conn.escape_string(str)
        self.assertEqual(newstr, "Bob''s")
        cursor.execute("SET session sql_mode=''")
        newstr = conn.escape_string(str)
        self.assertEqual(newstr, "Bob\\'s")
        conn.close()

    def test_closed(self):
        default_conf = conf()
        conn = mariadb.connect(**default_conf)
        conn.close()
        try:
            conn.cursor()
        except (mariadb.ProgrammingError):
            pass

    def test_multi_host(self):
        default_conf = conf()
        default_conf["host"] = "non_existant," + default_conf["host"]
        default_conf["connect_timeout"] = 1
        try:
            mariadb.connect(**default_conf)
        except mariadb.ProgrammingError:
            self.assertLess(parse_version(mariadb.mariadbapi_version),
                            parse_version('3.3.0'))
            pass

    def test_tls_verification(self):
        if is_maxscale():
            self.skipTest("MAXSCALE test has no SSL on port by default")
        if mariadb.mariadbapi_version  is not None and version.Version(mariadb.mariadbapi_version) <\
               version.Version('3.4.2'):
            self.skipTest("Requires C/C 3.4.2 or newer")
        default_conf= conf()
        default_conf["ssl"] = False
        conn= mariadb.connect(**default_conf)
        self.assertEqual(conn._tls_verify_status, None)
        conn.close()
        default_conf= conf()
        default_conf["ssl"] = True 
        conn= mariadb.connect(**default_conf)
        self.assertNotEqual(conn._tls_verify_status, None)
        conn.close()

    def test_tls_fp(self):
        if is_maxscale():
            self.skipTest("MAXSCALE test has no SSL on port by default")
        if mariadb.mariadbapi_version is not None and version.Version(mariadb.mariadbapi_version) <\
               version.Version('3.4.2'):
            self.skipTest("Requires C/C 3.4.2 or newer")
        default_conf= conf()
        default_conf["ssl"] = True
        conn= mariadb.connect(**default_conf)
        self.assertEqual(conn._tls, True)
        
        # Verify TLS cipher and version are set
        self.assertIsNotNone(conn.tls_cipher)
        self.assertIsNotNone(conn.tls_version)
        
        x509_info= conn.tls_peer_cert_info
        if not x509_info:
            conn.close()
            self.skipTest("Peer certificate information not supported")
        fp= x509_info["fingerprint"]
        self.assertEqual(len(fp), 64)
        conn.close()
        default_conf= conf()
        default_conf["tls_fp"] = fp
        conn= mariadb.connect(**default_conf)
        self.assertEqual(conn._tls, True)
        
        # Verify TLS cipher and version are set on reconnection
        self.assertIsNotNone(conn.tls_cipher)
        self.assertIsNotNone(conn.tls_version)
        
        x509_info= conn.tls_peer_cert_info
        self.assertEqual(fp, x509_info["fingerprint"])
        conn.close()

    def test_conpy278(self):
        if is_maxscale():
           self.skipTest("MAXSCALE bug MXS-4961")
        if is_native() or mariadb.__impl__ in ('c', 'binary'):
           self.skipTest("reconnect option not supported")
        with create_connection({"reconnect" : True}) as conn:
            old_id= conn.connection_id
            try:
                conn.kill("a")
            except mariadb.ProgrammingError:
                pass

            old_id= conn.connection_id
            try:
                conn.kill(conn.connection_id)
            except mariadb.OperationalError:
                conn.ping()
            self.assertNotEqual(old_id, conn.connection_id)
        with create_connection({"reconnect" : True}) as conn:
            old_id= conn.connection_id
            try:
                conn.kill(conn.connection_id)
            except mariadb.OperationalError:
                conn.ping()
            self.assertNotEqual(old_id, conn.connection_id)
        with create_connection({"reconnect" : True}) as conn:
            old_id= conn.connection_id
            try:
                conn.kill(conn.connection_id)
            except mariadb.OperationalError:
                pass
            with conn.cursor() as cursor:
                try:
                    cursor.execute("set @a:=1")
                except mariadb.InterfaceError:
                    pass
                cursor.execute("set @a:=1")
                self.assertNotEqual(old_id, conn.connection_id)
            old_id= conn.connection_id
            conn.reconnect()
            self.assertNotEqual(old_id, conn.connection_id)

    def test_tls_properties_non_ssl(self):
        """Test that all TLS properties return correct default values when SSL is not enabled"""
        default_conf = conf()
        default_conf["ssl"] = False
        conn = mariadb.connect(**default_conf)
        
        try:
            # Test _tls property - should be False for non-SSL connection
            self.assertFalse(conn._tls)
            
            # Test _tls_verify_status - should be None for non-SSL connection
            self.assertIsNone(conn._tls_verify_status)
            
            # Test tls_version - should be None for non-SSL connection
            self.assertIsNone(conn.tls_version)
            
            # Test tls_cipher - should be None for non-SSL connection
            self.assertIsNone(conn.tls_cipher)
            
            # Test tls_peer_cert_info - should be None for non-SSL connection
            self.assertIsNone(conn.tls_peer_cert_info)
        finally:
            conn.close()

    def test_connection_host_port_properties(self):
        """Test that connection.host and connection.port return expected values"""
        default_conf = conf()
        
        # Test with default connection
        conn = create_connection()
        try:
            self.assertEqual(conn.server_name, default_conf["host"])
            self.assertEqual(conn.server_port, default_conf["port"])
        finally:
            conn.close()
        with self.assertRaises((mariadb.InterfaceError)):
            conn.connection_id

    def test_capabilities(self):
        """Test client_capabilities and server_capabilities properties"""
        conn = create_connection()
        try:
            # Test client_capabilities
            client_caps = conn.client_capabilities
            self.assertIsInstance(client_caps, int)
            
            # Test server_capabilities
            server_caps = conn.server_capabilities
            self.assertIsInstance(server_caps, int)
            
            # Verify some common capability flags are set
            # CLIENT_PROTOCOL_41 = 512
            # CLIENT_TRANSACTIONS = 8192
            self.assertTrue(client_caps & 512)  # CLIENT_PROTOCOL_41
            
            # Server should also have these capabilities
            self.assertTrue(server_caps & 512)  # CLIENT_PROTOCOL_41
            
        finally:
            conn.close()
        
        # Test that accessing capabilities on closed connection raises error
        with self.assertRaises(mariadb.ProgrammingError):
            _ = conn.client_capabilities
        
        with self.assertRaises(mariadb.ProgrammingError):
            _ = conn.server_capabilities

    def test_begin(self):
        """Test begin() method to explicitly start a transaction"""
        conn = create_connection()
        try:
            # Test 1: begin() should work and start a transaction
            conn.begin()
            
            cursor = conn.cursor()
            cursor.execute("CREATE TEMPORARY TABLE test_begin (id INT, value VARCHAR(50))")
            cursor.execute("INSERT INTO test_begin VALUES (1, 'test')")
            
            # Verify data is visible in same transaction
            cursor.execute("SELECT * FROM test_begin")
            result = cursor.fetchone()
            self.assertEqual(result[0], 1)
            self.assertEqual(result[1], 'test')
            
            # Rollback the transaction
            conn.rollback()
            
            # Verify table still exists but data is rolled back
            cursor.execute("SELECT COUNT(*) FROM test_begin")
            count = cursor.fetchone()[0]
            self.assertEqual(count, 0)
            
            # Test 2: Multiple begin() calls should work
            conn.begin()
            cursor.execute("INSERT INTO test_begin VALUES (2, 'test2')")
            conn.begin()  # Should not fail
            cursor.execute("INSERT INTO test_begin VALUES (3, 'test3')")
            conn.commit()
            
            # Verify both inserts are committed
            cursor.execute("SELECT COUNT(*) FROM test_begin")
            count = cursor.fetchone()[0]
            self.assertEqual(count, 2)
            
            cursor.close()
        finally:
            conn.close()

    def test_begin_with_autocommit(self):
        """Test begin() behavior with autocommit enabled"""
        default_conf = conf()
        default_conf["autocommit"] = True
        conn = mariadb.connect(**default_conf)
        
        try:
            # begin() should work even with autocommit enabled
            conn.begin()
            
            cursor = conn.cursor()
            cursor.execute("CREATE TEMPORARY TABLE test_begin_autocommit (id INT)")
            cursor.execute("INSERT INTO test_begin_autocommit VALUES (1)")
            
            # Rollback should work after begin()
            conn.rollback()
            
            cursor.execute("SELECT COUNT(*) FROM test_begin_autocommit")
            count = cursor.fetchone()[0]
            self.assertEqual(count, 0)
            
            cursor.close()
        finally:
            conn.close()

    def test_multiple_hosts(self):
        default_conf = conf()
        default_conf["host"] = "non_existant," + default_conf["host"]
        default_conf["connect_timeout"] = 1
        new_conn = mariadb.connect(**default_conf)
        cursor = new_conn.cursor()
        cursor.execute("SELECT 1")
        row = cursor.fetchone()
        self.assertEqual(row[0], 1)
        del cursor
        del new_conn

    def test_ssl_dict_compatibility(self):
        """Test SSL dictionary compatibility feature (mariadb-c compatibility)"""
        default_conf = conf()
        
        # Test 1: SSL as dictionary with ca, cert, key, cipher, capath
        ssl_dict = {
            "ca": "/path/to/ca.pem",
            "cert": "/path/to/cert.pem",
            "key": "/path/to/key.pem",
            "cipher": "AES256-SHA",
            "capath": "/path/to/capath"
        }
        
        # Create config with SSL dictionary
        test_conf = default_conf.copy()
        test_conf["ssl"] = ssl_dict
        
        # The connection will fail because the SSL files don't exist,
        # but we can verify the parameters were correctly mapped
        try:
            conn = mariadb.connect(**test_conf)
            conn.close()
        except (mariadb.OperationalError, mariadb.DatabaseError, OSError) as e:
            # Expected to fail with SSL file errors, but verify the error
            # is about SSL files, not about invalid parameters
            error_msg = str(e).lower()
            # Should fail with SSL-related error, not parameter error
            self.assertTrue(
                'ssl' in error_msg or 
                'certificate' in error_msg or 
                'tls' in error_msg or
                'file' in error_msg or
                'path' in error_msg,
                f"Expected SSL-related error, got: {e}"
            )
        
        # Test 2: Verify ssl parameter is converted to True
        test_conf2 = default_conf.copy()
        test_conf2["ssl"] = {"ca": "/nonexistent.pem"}
        
        # After processing, ssl should be True and ssl_ca should be set
        # We can't easily verify this without modifying the connect function,
        # but the fact that it tries to use SSL (and fails) proves it worked
        try:
            conn = mariadb.connect(**test_conf2)
            conn.close()
        except (mariadb.OperationalError, mariadb.DatabaseError, OSError):
            # Expected - SSL files don't exist
            pass
        
        # Test 3: Empty SSL dictionary should just enable SSL
        test_conf3 = default_conf.copy()
        test_conf3["ssl"] = {}
        
        try:
            # This might succeed if server supports SSL without client certs
            conn = mariadb.connect(**test_conf3)
            # If it succeeds, verify SSL is enabled
            self.assertTrue(conn._tls or True)  # Connection succeeded
            conn.close()
        except (mariadb.OperationalError, mariadb.DatabaseError):
            # Expected if server doesn't support SSL or requires certs
            pass

    def test_ssl_fingerprint_validation(self):
        
        if self.connection.server_version < 110401 or (platform.system() == "Windows" and not is_native()):
            self.skipTest(f"SSL fingerprint validation requires MariaDB >= 11.4.1")
        
        # Check if cryptography package is available for PARSEC
        has_cryptography = False
        try:
            from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
            has_cryptography = True
        except ImportError:
            pass
        
        default_conf = conf()
        
        test_users = [
            ('fp_native_user', 'heyPassw-!*20oRd', 'mysql_native_password'),
        ]
        
        # Check if server supports caching_sha2_password (MariaDB >= 12.1.1)
        if self.connection.server_version >= 120101:
           with self.connection.cursor() as cursor:
               try:
                   cursor.execute("INSTALL SONAME 'auth_mysql_sha2'")
               except:
                   pass
           test_users.append(('fp_sha2_user', 'heyPassw-!*20oRd', 'caching_sha2_password'))


        local_host_names = ["127.0.0.1", "::1"]
        if platform.system() == "Windows":
            local_host_names.append("localhost")
        is_local = default_conf['host'] in local_host_names

        has_parsec = False
        if has_cryptography:
            with self.connection.cursor() as cursor:
                cursor.execute("SELECT PLUGIN_NAME FROM information_schema.PLUGINS WHERE PLUGIN_NAME='parsec'")
                has_parsec = cursor.fetchone() is not None
        if has_parsec:
            test_users.append(('fp_parsec_user', 'heyPassw-!*20oRd', 'parsec'))
        
        try:
            # Create test users
            with self.connection.cursor() as cursor:
                for username, password, plugin in test_users:
                    # Drop user if exists
                    try:
                        cursor.execute(f"DROP USER IF EXISTS '{username}'" + get_host_suffix())
                    except:
                        pass
                    
                    # Create user with specific plugin
                    cursor.execute(
                        f"CREATE USER '{username}'{get_host_suffix()} IDENTIFIED VIA {plugin} USING PASSWORD('{password}')"
                    )
                    
                    # Grant privileges
                    cursor.execute(f"GRANT ALL PRIVILEGES ON *.* TO '{username}'{get_host_suffix()}")
                
                cursor.execute("FLUSH PRIVILEGES")
                self.connection.commit()
            
            # Test 1: SSL connection with password and no ssl_ca (should use fingerprint validation)
            for username, password, plugin in test_users:
                test_conf = default_conf.copy()
                test_conf['user'] = username
                test_conf['password'] = password
                test_conf['ssl'] = True
                test_conf['ssl_verify_cert'] = True  # Enable certificate verification
                # Remove ssl_ca to trigger fingerprint validation
                test_conf.pop('ssl_ca', None)
                test_conf.pop('ssl_cert', None)
                test_conf.pop('ssl_key', None)
                
                try:
                    try:
                        conn = mariadb.connect(**test_conf)
                        if (plugin == 'caching_sha2_password' and not is_local):
                            self.fail(f"SSL fingerprint must have failed {username} ({plugin})")
                    except mariadb.OperationalError as e:
                        if (plugin == 'caching_sha2_password' and not is_local):
                            continue
                        else:
                            raise e
                    # Connection should succeed with fingerprint validation
                    cursor = conn.cursor()
                    cursor.execute("SELECT 1")
                    result = cursor.fetchone()
                    self.assertEqual(result[0], 1, 
                        f"Connection with {plugin} and fingerprint validation should work")
                    cursor.close()
                    conn.close()
                except mariadb.OperationalError as e:
                    # If it fails, it should be due to SSL configuration, not fingerprint
                    self.fail(f"SSL fingerprint validation failed for {username} ({plugin}): {e}")

            with self.connection.cursor() as cursor:
                # Create user with specific plugin
                server_permits_no_password = True
                try:
                    cursor.execute(f"DROP USER IF EXISTS 'fp_nopass_user'" + get_host_suffix())
                except:
                    pass
                try:
                    cursor.execute(
                        f"CREATE USER 'fp_nopass_user'{get_host_suffix()} IDENTIFIED VIA mysql_native_password"
                    )
                    cursor.execute(f"GRANT ALL PRIVILEGES ON *.* TO 'fp_nopass_user'{get_host_suffix()}")
                except:
                    server_permits_no_password = False
                    pass
                
                cursor.execute("FLUSH PRIVILEGES")
                self.connection.commit()     

            # Test 2: SSL connection without password (should succeed but not use fingerprint validation)
            if server_permits_no_password and default_conf['host'] != 'localhost' and default_conf['host'] != '127.0.0.1':
                test_conf = default_conf.copy()
                test_conf['user'] = "fp_nopass_user"
                test_conf['password'] = ''
                test_conf['ssl'] = True
                test_conf['ssl_verify_cert'] = True
                test_conf.pop('ssl_ca', None)
                test_conf.pop('ssl_cert', None)
                test_conf.pop('ssl_key', None)
                
                with self.assertRaises(mariadb.OperationalError) as cm:
                    conn = mariadb.connect(**test_conf)

                # Should fail because fingerprint validation requires password
                error_msg = str(cm.exception)
                self.assertTrue('Failed to upgrade socket to SSL' in error_msg or 
                    'self-signed certificate' in error_msg or
                    'self signed certificate' in error_msg, 
                    error_msg)
            
        finally:
            # Cleanup: Drop test users
            for username, _, _ in test_users:
                try:
                    cursor.execute(f"DROP USER IF EXISTS '{username}'@'%'")
                except:
                    pass
            
            cursor.close()

    def test_pre41_error_format(self):
        """Test handling of pre-4.1 error format when max connections is reached"""
        # Skip for MaxScale
        if is_maxscale():
            self.skipTest("Skipping for MaxScale")
        
        exception = None
        max_connections = 0
        
        # Get max_connections setting
        cursor = self.connection.cursor()
        cursor.execute("SELECT @@max_connections")
        result = cursor.fetchone()
        max_connections = result[0]
        cursor.close()
        
        # Skip if max_connections is too high (would take too long)
        if max_connections >= 1000:
            self.skipTest(f"max_connections too high ({max_connections}), skipping test")
        
        connections = []
        try:
            # Try to create max_connections connections
            for i in range(max_connections + 1):
                try:
                    conn = mariadb.connect(**conf())
                    connections.append(conn)
                except mariadb.DatabaseError as e:
                    exception = e
                    break
            
            # Should have gotten an exception
            self.assertIsNotNone(exception, "Expected exception when reaching max_connections")
            
            # Check error message contains "Too many"
            error_msg = str(exception)
            self.assertTrue(
                "Too many" in error_msg or "too many" in error_msg,
                f"Expected 'Too many' in error message, got: {error_msg}"
            )
        
        finally:
            # Clean up all connections
            for conn in connections:
                try:
                    if conn:
                        conn.close()
                except:
                    pass

    def test_ssl_connection_with_ca(self):
        """Test SSL connection with server CA certificate"""
        import os
        
        # Skip if TEST_DB_SERVER_CERT is not set
        server_cert = os.environ.get('TEST_DB_SERVER_CERT')
        if not server_cert:
            self.skipTest("TEST_DB_SERVER_CERT not set, skipping SSL test")
        
        # Skip if certificate file doesn't exist
        if not os.path.exists(server_cert):
            self.skipTest(f"Server certificate file not found: {server_cert}")
        
        default_conf = conf()
        
        # Test SSL connection with CA certificate
        test_conf = default_conf.copy()
        test_conf['ssl'] = True
        test_conf['ssl_ca'] = server_cert
        test_conf['ssl_verify_cert'] = True
        
        try:
            conn = mariadb.connect(**test_conf)
            
            # Verify SSL is active
            cursor = conn.cursor()
            cursor.execute("SHOW STATUS LIKE 'Ssl_cipher'")
            result = cursor.fetchone()
            self.assertIsNotNone(result)
            self.assertNotEqual(result[1], '', "SSL cipher should not be empty")
            
            cursor.close()
            conn.close()
        except mariadb.Error as e:
            self.fail(f"SSL connection with CA failed: {e}")


if __name__ == '__main__':
    unittest.main()
