#
#  Licensed Materials - Property of IBM
#
#  (c) Copyright IBM Corp. 2024
#
#  Test script for ibm_db_dbi Two-Phase Commit (DUOW) support.
#
#  Tests the DBI-level wrappers:
#    - CoordinatedConnection: commit / rollback / close / context manager
#

from __future__ import print_function
import sys
import unittest
import ibm_db
import config
from testfunctions import IbmDbTestFunctions

# Build DSN connection string from config
if hasattr(config, 'hostname') and config.hostname:
    CONN_STR1 = (f"DATABASE={config.database};HOSTNAME={config.hostname};"
                 f"PORT={config.port};PROTOCOL=TCPIP;"
                 f"UID={config.user};PWD={config.password}")
else:
    CONN_STR1 = config.database

# Set a second connection string directly in this suite when validating cross-database 2PC.
# Leave blank to reuse CONN_STR1.
CONN_STR2_OVERRIDE = ""
CONN_STR2 = CONN_STR2_OVERRIDE if CONN_STR2_OVERRIDE else CONN_STR1


class IbmDbTestCase(unittest.TestCase):

    def test_two_phase_commit_dbi(self):
        obj = IbmDbTestFunctions()
        obj.assert_expectf(self.run_test_two_phase_commit_dbi)

    def run_test_two_phase_commit_dbi(self):
        """Run DBI-level two-phase commit tests."""

        # ---------------------------------------------------------- #
        #  TEST 1: DBI CoordinatedConnection - commit and rollback
        # ---------------------------------------------------------- #
        print("TEST 1: DBI CoordinatedConnection")
        from ibm_db_dbi import CoordinatedConnection

        cc = CoordinatedConnection()
        c1 = cc.connect(CONN_STR1, '', '')
        cur1 = c1.cursor()

        try:
            cur1.execute("DROP TABLE tpc_dbi1")
            cc.commit()
        except Exception:
            try:
                cc.rollback()
            except Exception:
                pass
        cur1.execute("CREATE TABLE tpc_dbi1 (id INT)")
        cc.commit()

        cur1.execute("INSERT INTO tpc_dbi1 VALUES (1)")
        cc.commit()

        cur1.execute("SELECT COUNT(*) FROM tpc_dbi1")
        row = cur1.fetchone()
        if row[0] == 1:
            print("  DBI commit: OK")
        else:
            print("  DBI commit: FAILED")

        cur1.execute("INSERT INTO tpc_dbi1 VALUES (2)")
        cc.rollback()

        cur1.execute("SELECT COUNT(*) FROM tpc_dbi1")
        row = cur1.fetchone()
        if row[0] == 1:
            print("  DBI rollback: OK")
        else:
            print("  DBI rollback: FAILED")

        cur1.execute("DROP TABLE tpc_dbi1")
        cc.commit()
        cc.close()

        # ---------------------------------------------------------- #
        #  TEST 2: DBI close is idempotent
        # ---------------------------------------------------------- #
        print("TEST 2: DBI close idempotent")
        cc = CoordinatedConnection()
        c1 = cc.connect(CONN_STR1, '', '')
        cc.close()
        try:
            cc.close()
            print("  double close: OK")
        except Exception:
            print("  double close: FAILED (exception raised)")

        # ---------------------------------------------------------- #
        #  TEST 3: DBI CoordinatedConnection - context manager
        # ---------------------------------------------------------- #
        print("TEST 3: DBI context manager (with)")
        with CoordinatedConnection() as cc:
            c1 = cc.connect(CONN_STR1, '', '')
            cur1 = c1.cursor()
            try:
                cur1.execute("DROP TABLE tpc_dbi_ctx")
                cc.commit()
            except Exception:
                try:
                    cc.rollback()
                except Exception:
                    pass
            cur1.execute("CREATE TABLE tpc_dbi_ctx (id INT)")
            cc.commit()
            cur1.execute("INSERT INTO tpc_dbi_ctx VALUES (1)")
            # No explicit commit - 'with' block auto-commits on exit
        # Verify via regular connection
        verify = ibm_db.connect(CONN_STR1, '', '')
        stmt = ibm_db.exec_immediate(verify, "SELECT COUNT(*) FROM tpc_dbi_ctx")
        count = ibm_db.fetch_tuple(stmt)[0]
        if int(count) == 1:
            print("  auto-commit on exit: OK")
        else:
            print("  auto-commit on exit: FAILED (count=%s)" % count)
        ibm_db.exec_immediate(verify, "DROP TABLE tpc_dbi_ctx")
        ibm_db.commit(verify)
        ibm_db.close(verify)

        # ---------------------------------------------------------- #
        #  TEST 4: DBI CoordinatedConnection - error on closed
        # ---------------------------------------------------------- #
        print("TEST 4: DBI error on closed connection")
        cc = CoordinatedConnection()
        cc.close()
        try:
            cc.connect(CONN_STR1, '', '')
            print("  connect after close: FAILED (no exception)")
        except Exception:
            print("  connect after close: OK (exception raised)")
        try:
            cc.commit()
            print("  commit after close: FAILED (no exception)")
        except Exception:
            print("  commit after close: OK (exception raised)")
        try:
            cc.rollback()
            print("  rollback after close: FAILED (no exception)")
        except Exception:
            print("  rollback after close: OK (exception raised)")

        # ---------------------------------------------------------- #
        #  TEST 5: DBI CoordinatedConnection - 2 connections
        # ---------------------------------------------------------- #
        print("TEST 5: DBI two connections coordinated")
        from ibm_db_dbi import CoordinatedConnection

        cc = CoordinatedConnection()
        c1 = cc.connect(CONN_STR1, '', '')
        cur1 = c1.cursor()

        # Clean up from any prior run
        try:
            cur1.execute("DROP TABLE tpc_dbi_2c1")
            cc.commit()
        except Exception:
            try:
                cc.rollback()
            except Exception:
                pass

        try:
            c2 = cc.connect(CONN_STR2, '', '')
        except Exception:
            # Second database unreachable - fall back to single-connection
            c2 = None

        if c2 is not None:
            cur2 = c2.cursor()

            try:
                cur2.execute("DROP TABLE tpc_dbi_2c2")
                cc.commit()
            except Exception:
                try:
                    cc.rollback()
                except Exception:
                    pass

            # Create tables on both databases
            cur1.execute("CREATE TABLE tpc_dbi_2c1 (id INT)")
            cur2.execute("CREATE TABLE tpc_dbi_2c2 (id INT)")
            cc.commit()

            # Insert on both, then commit atomically
            cur1.execute("INSERT INTO tpc_dbi_2c1 VALUES (1)")
            cur2.execute("INSERT INTO tpc_dbi_2c2 VALUES (100)")
            cc.commit()

            cur1.execute("SELECT COUNT(*) FROM tpc_dbi_2c1")
            cnt1 = cur1.fetchone()[0]
            cur2.execute("SELECT COUNT(*) FROM tpc_dbi_2c2")
            cnt2 = cur2.fetchone()[0]
            if int(cnt1) == 1 and int(cnt2) == 1:
                print("  2-conn commit: OK")
            else:
                print("  2-conn commit: FAILED (cnt1=%s, cnt2=%s)" % (cnt1, cnt2))

            # Insert on both, then rollback atomically
            cur1.execute("INSERT INTO tpc_dbi_2c1 VALUES (2)")
            cur2.execute("INSERT INTO tpc_dbi_2c2 VALUES (200)")
            cc.rollback()

            cur1.execute("SELECT COUNT(*) FROM tpc_dbi_2c1")
            cnt1 = cur1.fetchone()[0]
            cur2.execute("SELECT COUNT(*) FROM tpc_dbi_2c2")
            cnt2 = cur2.fetchone()[0]
            if int(cnt1) == 1 and int(cnt2) == 1:
                print("  2-conn rollback: OK")
            else:
                print("  2-conn rollback: FAILED (cnt1=%s, cnt2=%s)" % (cnt1, cnt2))

            # Cleanup
            cur1.execute("DROP TABLE tpc_dbi_2c1")
            cur2.execute("DROP TABLE tpc_dbi_2c2")
            cc.commit()
        else:
            # Only one database available - test with single connection
            cur1.execute("CREATE TABLE tpc_dbi_2c1 (id INT)")
            cc.commit()

            cur1.execute("INSERT INTO tpc_dbi_2c1 VALUES (1)")
            cc.commit()

            cur1.execute("SELECT COUNT(*) FROM tpc_dbi_2c1")
            cnt1 = cur1.fetchone()[0]
            if int(cnt1) == 1:
                print("  2-conn commit: OK")
            else:
                print("  2-conn commit: FAILED (cnt=%s)" % cnt1)

            cur1.execute("INSERT INTO tpc_dbi_2c1 VALUES (2)")
            cc.rollback()

            cur1.execute("SELECT COUNT(*) FROM tpc_dbi_2c1")
            cnt1 = cur1.fetchone()[0]
            if int(cnt1) == 1:
                print("  2-conn rollback: OK")
            else:
                print("  2-conn rollback: FAILED (cnt=%s)" % cnt1)

            cur1.execute("DROP TABLE tpc_dbi_2c1")
            cc.commit()

        cc.close()

        print("All DBI two-phase commit tests completed.")


#__LUW_EXPECTED__
#TEST 1: DBI CoordinatedConnection
#  DBI commit: OK
#  DBI rollback: OK
#TEST 2: DBI close idempotent
#  double close: OK
#TEST 3: DBI context manager (with)
#  auto-commit on exit: OK
#TEST 4: DBI error on closed connection
#  connect after close: OK (exception raised)
#  commit after close: OK (exception raised)
#  rollback after close: OK (exception raised)
#TEST 5: DBI two connections coordinated
#  2-conn commit: OK
#  2-conn rollback: OK
#All DBI two-phase commit tests completed.
