package database

import java.sql.Connection
import java.sql.DriverManager
import config
import org.tinylog.Logger
import java.util.function.BiConsumer
import java.util.function.Consumer

@Suppress("UNUSED")
/**
 * MySQL Adapter for AAS
 * @param aas_id The AAS ID (1, 2, or 3)
 */
class MySQLAdapter(val aas_id: Int) {
    private var _verified  = false
    private var _successcount = 0
    private var _failcount = 0
    private val _broadcastzones = ArrayList<String>()
    private val _airlinecodes = ArrayList<String>()
    private val _queueTableData = ArrayList<QueueTableData>()

    /**
     * Connection Verified
     */
    val ConnectionVerified : Boolean
        get() = _verified

    /**
     * Successful transactions count
     */
    val SuccessCount: Int
        get() = _successcount

    /**
     * Failed transactions count
     */
    val FailCount: Int
        get() = _failcount

    init{
        require(aas_id in 1..3){
            "aas_id must be between 1 and 3"
        }
    }

    /**
     * Last Result of Get Broadcast Zones
     */
    val BroadcastZones : ArrayList<String>
        get() = _broadcastzones

    /**
     * Last Result of Get Airline Codes
     */
    val AirlineCodes : ArrayList<String>
        get() = _airlinecodes

    /**
     * Last Result of Get Queue Table
     */
    val QueueTable: ArrayList<QueueTableData>
        get() = _queueTableData

    /**
     * Try to connect to MySQL Database
     * @param cb Callback function with parameters (success: Boolean, connection: Connection)
     */
    private fun Connect(cb : BiConsumer<Boolean,Connection?>){
        try {
            val conn = when(aas_id){
                1 -> DriverManager.getConnection(config.MySQL_AAS1, config.MySQL_AAS1_Username, config.MySQL_AAS1_Password)
                2 -> DriverManager.getConnection(config.MySQL_AAS2, config.MySQL_AAS2_Username, config.MySQL_AAS2_Password)
                3 -> DriverManager.getConnection(config.MySQL_AAS3, config.MySQL_AAS3_Username, config.MySQL_AAS3_Password)
                else -> null
            }
            if (conn != null){
                cb.accept(true, conn)
                _verified = true
            } else throw Exception("Connection is null")
        } catch (e : Exception){
            Logger.error { "Failed to connect MySQL Adapter for AAS$aas_id: ${e.message}" }
            _verified = false
            cb.accept(false, null)
        }
    }

    /**
     * Verify Connection to MySQL Database
     * @param cb Callback function with parameter (success: Boolean)
     */
    fun VerifyConnection(cb: Consumer<Boolean>){
        Connect { success, connection ->
            if (success){
                connection!!.close()
                _verified = true
                cb.accept(true)
            } else {
                _verified = false
                cb.accept(false)
            }
        }
    }

    /**
     * Get Broadcast Zones
     * @param cb Callback function (optional) with parameters (success: Boolean, zones: ArrayList<String
     */
    fun GetBroadcastZones(cb: BiConsumer<Boolean, ArrayList<String>>? = null){
        Connect { success, connection ->
            if (success){
                try {
                    connection!!.prepareStatement("SELECT DISTINCT description from aas.broadcastzones").use { statement ->
                        val result = ArrayList<String>()
                        statement.executeQuery().use { rs ->
                            while (rs.next()){
                                result.add(rs.getString("ZONE"))
                            }
                        }
                        connection.close()
                        Logger.info { "GetBroadcastZones succeeded, found ${result.size} zones" }
                        cb?.accept(true, result)
                        _successcount++
                        _broadcastzones.clear()
                        _broadcastzones.addAll(result)
                    }
                } catch (e: Exception){
                    Logger.error { "GetBroadcastZones failed, exception: ${e.message}" }
                    cb?.accept(false, arrayListOf())
                    _failcount++
                }

            } else {
                Logger.error { "GetBroadcastZones failed, no Connection" }
                cb?.accept(false, arrayListOf())
                _failcount++
            }
        }
    }

    /**
     * Get Airline Codes
     * @param cb Callback function (optional) with parameters (success: Boolean, codes: ArrayList<String>)
     */
    fun GetAirlineCode(cb: BiConsumer<Boolean, ArrayList<String>>? = null){
        Connect { success, connection ->
            if (success){
                try {
                    connection!!.prepareStatement("SELECT DISTINCT TAG from aas.soundbank").use { statement ->
                        val result = ArrayList<String>()
                        statement.executeQuery().use { rs ->
                            while (rs.next()){
                                result.add(rs.getString("TAG"))
                            }
                        }
                        connection.close()
                        Logger.info { "GetAirlineCode succeeded, found ${result.size} codes" }
                        cb?.accept(true, result)
                        _successcount++
                        _airlinecodes.clear()
                        _airlinecodes.addAll(result)
                    }
                } catch (e: Exception){
                    Logger.error { "GetAirlineCode failed, exception: ${e.message}" }
                    cb?.accept(false, arrayListOf())
                    _failcount++
                }

            } else {
                Logger.error { "GetAirlineCode failed, no Connection" }
                cb?.accept(false, arrayListOf())
                _failcount++
            }
        }
    }

    /**
     * Get Queue Table records
     * @param cb Callback function (optional) with parameters (success: Boolean, records: ArrayList<QueueTableData>)
     */
    fun GetQueueTable(cb: BiConsumer<Boolean, ArrayList<QueueTableData>>? = null){
        Connect { success, connection ->
            if (success){
                try {
                    connection!!.prepareStatement("SELECT * from aas.queue_table").use { statement ->
                        val result = ArrayList<QueueTableData>()
                        statement.executeQuery().use { rs ->
                            while (rs.next()){
                                result.add(
                                    QueueTableData(
                                        Date_Time = rs.getString("Date_Time"),
                                        Source = rs.getString("Source"),
                                        Tipe = rs.getString("Type"),
                                        Message = rs.getString("Message"),
                                        SB_Tags = rs.getString("SB_TAGS"),
                                        BroadcastZones = rs.getString("BroadcastZones"),
                                        Language = rs.getString("Language")
                                    )
                                )
                            }
                        }
                        connection.close()
                        Logger.info { "GetQueueTable succeeded, found ${result.size} records" }
                        cb?.accept(true, result)
                        _successcount++
                        _queueTableData.clear()
                        _queueTableData.addAll(result)
                    }
                } catch (e: Exception){
                    Logger.error { "GetQueueTable failed, exception: ${e.message}" }
                    cb?.accept(false, arrayListOf())
                    _failcount++
                }

            } else {
                Logger.error { "GetQueueTable failed, no Connection" }
                cb?.accept(false, arrayListOf())
                _failcount++
            }
        }
    }

    /**
     * Clear Queue Table
     * @param cb Callback function with parameter (success: Boolean)
     */
    fun ClearQueueTable(cb: Consumer<Boolean>){
        Connect { success, connection ->
            if (success){
                try {
                    connection!!.prepareStatement("TRUNCATE TABLE aas.queue_table").use { statement ->
                        val rowsAffected = statement.executeUpdate()
                        connection.close()
                        Logger.info { "ClearQueueTable succeeded, deleted $rowsAffected records" }
                        cb.accept(true)
                        _successcount++
                    }
                } catch (e: Exception){
                    Logger.error { "ClearQueueTable failed, exception: ${e.message}" }
                    cb.accept(false)
                    _failcount++
                }

            } else {
                Logger.error { "ClearQueueTable failed, no Connection" }
                cb.accept(false)
                _failcount++
            }
        }
    }

    /**
     * Delete records from Queue Table by index
     * @param index Vararg of record IDs to delete
     * @param cb Callback function with parameter (success: Boolean)
     */
    fun DeleteQueueTable(vararg index: Int, cb: Consumer<Boolean>){
        Connect { success, connection ->
            if (success){
                try{
                    var updatecount : IntArray
                    connection!!.prepareStatement("DELETE FROM aas.queue_table WHERE id = ?").use { ps ->
                        index.forEach { id ->
                            ps.setInt(1, id)
                            ps.addBatch()
                        }
                        updatecount = ps.executeBatch()
                        ps.close()
                    }

                    connection.close()
                    Logger.info { "Deleted ${index.size} records from AAS$aas_id Queue Table, update counts: ${updatecount.joinToString()}" }
                    cb.accept(true)
                    _successcount++
                } catch (e : Exception){
                    Logger.error { "Failed to delete MySQL Adapter for AAS$aas_id: ${e.message}" }
                    cb.accept(false)
                    _failcount++
                }

            } else {
                Logger.error { "DeleteQueueTable failed , no Connection" }
                cb.accept(false)
                _failcount++
            }
        }
    }

    /**
     * Insert records to Queue Table
     * @param queue Vararg of QueueTableData to insert
     * @param cb Callback function with parameter (success: Boolean)
     */
    fun InsertToQueueTable(vararg queue: QueueTableData, cb: Consumer<Boolean>){
        Connect { success, connection ->
            if (success){
                var updatecount : IntArray
                try{
                    connection!!.prepareStatement("INSERT INTO aas.queue_table (Date_Time, Source, Type, Message, SB_TAGS, BroadcastZones, Repeat, Language) VALUES (?,?,?,?,?,?,?,?)").use { ps ->
                        queue.forEach { data ->
                            ps.setString(1, data.Date_Time)
                            ps.setString(2, data.Source)
                            ps.setString(3, data.Tipe)
                            ps.setString(4, data.Message)
                            ps.setString(5, data.SB_Tags)
                            ps.setString(6, data.BroadcastZones)
                            ps.setInt(7, 1)
                            ps.setString(8, data.Language)
                            ps.addBatch()
                        }
                        updatecount = ps.executeBatch()
                        ps.close()
                    }

                    connection.close()
                    Logger.info { "Inserted ${queue.size} records to AAS$aas_id Queue Table, update counts: ${updatecount.joinToString()}" }
                    cb.accept(true)
                    _successcount++
                } catch (e : Exception){
                    Logger.error { "Failed to insert MySQL Adapter for AAS$aas_id: ${e.message}" }
                    cb.accept(false)
                    _failcount++
                }

            } else {
                Logger.error { "InsertToQueueTable failed , no Connection" }
                cb.accept(false)
                _failcount++
            }
        }
    }

    /**
     * Create Flight Filter Table if not exists
     * @param cb Callback function with parameter (success: Boolean)
     */
    fun CreateFlightFilterTable(cb: Consumer<Boolean>){
        Connect { success, connection ->
            if (success){
                try {
                    connection!!.prepareStatement(
                        """
                        CREATE TABLE IF NOT EXISTS aas.tblFlightFilter (
                            id INT AUTO_INCREMENT PRIMARY KEY,
                            description VARCHAR(35) NOT NULL,
                            airlineCode VARCHAR(4) NOT NULL,
                            flightNumber VARCHAR(4) NOT NULL,
                            departureZone VARCHAR(512) NOT NULL,
                            arrivalZone VARCHAR(512) NOT NULL
                        );
                        """.trimIndent()
                    ).use { statement ->
                        statement.executeUpdate()
                        connection.close()
                        Logger.info { "CreateFlightFilterTable succeeded or already exists" }
                        cb.accept(true)
                        _successcount++
                    }
                } catch (e: Exception){
                    Logger.error { "CreateFlightFilterTable failed, exception: ${e.message}" }
                    cb.accept(false)
                    _failcount++
                }

            } else {
                Logger.error { "CreateFlightFilterTable failed, no Connection" }
                cb.accept(false)
                _failcount++
            }
        }
    }

    /**
     * Clear Flight Filter Table
     * @param cb Callback function with parameter (success: Boolean)
     */
    fun ClearFlightFilterTable(cb: Consumer<Boolean>){
        Connect { success, connection ->
            if (success){
                try {
                    connection!!.prepareStatement("TRUNCATE TABLE aas.tblFlightFilter").use { statement ->
                        val rowsAffected = statement.executeUpdate()
                        connection.close()
                        Logger.info { "ClearFlightFilterTable succeeded, deleted $rowsAffected records" }
                        cb.accept(true)
                        _successcount++
                    }
                } catch (e: Exception){
                    Logger.error { "ClearFlightFilterTable failed, exception: ${e.message}" }
                    cb.accept(false)
                    _failcount++
                }

            } else {
                Logger.error { "ClearFlightFilterTable failed, no Connection" }
                cb.accept(false)
                _failcount++
            }
        }
    }

    /**
     * Get Flight Filter Table records
     * @param cb Callback function with parameters (success: Boolean, records: ArrayList<FlightFilter>)
     */
    fun GetFlightFilterTable(cb: BiConsumer<Boolean, ArrayList<FlightFilter>>){
        Connect { success, connection ->
            if (success){
                try{
                    connection!!.prepareStatement("SELECT * FROM aas.tblFlightFilter").use { statement ->
                        statement.executeQuery().use { rs ->
                            val result = ArrayList<FlightFilter>()
                            while (rs.next()){
                                result.add(
                                    FlightFilter(
                                        id = rs.getInt("id"),
                                        Description = rs.getString("description"),
                                        AirlineCode = rs.getString("airlineCode"),
                                        FlightNumber = rs.getString("flightNumber"),
                                        DepartureZone = rs.getString("departureZone"),
                                        ArrivalZone = rs.getString("arrivalZone")
                                    )
                                )
                            }
                            connection.close()
                            Logger.info { "GetFlightFilterTable succeeded, found ${result.size} records" }
                            cb.accept(true, result)
                            _successcount++
                        }
                    }
                } catch (e : Exception){
                    Logger.error { "GetFlightFilterTable failed, exception: ${e.message}" }
                    cb.accept(false, arrayListOf())
                    _failcount++
                }

            } else {
                Logger.error { "GetFlightFilterTable failed, no Connection" }
                cb.accept(false, arrayListOf())
                _failcount++
            }
        }
    }

    /**
     * Patch record in Flight Filter Table by index
     * @param index Record ID to patch
     * @param flight FlightFilter data to update
     * @param cb Callback function with parameter (success: Boolean)
     */
    fun PatchFlightFilter(index: Int, flight : FlightFilter, cb: Consumer<Boolean>){
        Connect { success, connection ->
            if (success){
                try{
                    connection!!.prepareStatement("UPDATE aas.tblFlightFilter SET description = ?, airlineCode = ?, flightNumber = ?, departureZone = ?, arrivalZone = ? WHERE id = ?").use { ps ->
                        ps.setString(1, flight.Description)
                        ps.setString(2, flight.AirlineCode)
                        ps.setString(3, flight.FlightNumber)
                        ps.setString(4, flight.DepartureZone)
                        ps.setString(5, flight.ArrivalZone)
                        ps.setInt(6, index)
                        val updatecount = ps.executeUpdate()
                        ps.close()

                        connection.close()
                        Logger.info { "Patched record $index in AAS$aas_id Flight Filter Table, update count: $updatecount" }
                        cb.accept(true)
                        _successcount++
                    }
                } catch (e : Exception){
                    Logger.error { "Failed to patch MySQL Adapter for AAS$aas_id Flight Filter: ${e.message}" }
                    cb.accept(false)
                    _failcount++
                }

            } else {
                Logger.error { "PatchFlightFilter failed , no Connection" }
                cb.accept(false)
                _failcount++
            }
        }
    }

    /**
     * Delete records from Flight Filter Table by index
     * @param index Vararg of record IDs to delete
     * @param cb Callback function with parameter (success: Boolean)
     */
    fun DeleteFlightFilter(vararg index: Int, cb: Consumer<Boolean>){
        Connect { success, connection ->
            if (success){
                try{
                    var updatecount : IntArray
                    connection!!.prepareStatement("DELETE FROM aas.tblFlightFilter WHERE id = ?").use { ps ->
                        index.forEach { id ->
                            ps.setInt(1, id)
                            ps.addBatch()
                        }
                        updatecount = ps.executeBatch()
                        ps.close()
                    }

                    connection.close()
                    Logger.info { "Deleted ${index.size} records from AAS$aas_id Flight Filter Table, update counts: ${updatecount.joinToString()}" }
                    cb.accept(true)
                    _successcount++
                } catch (e : Exception){
                    Logger.error { "Failed to delete MySQL Adapter for AAS$aas_id Flight Filter: ${e.message}" }
                    cb.accept(false)
                    _failcount++
                }

            } else {
                Logger.error { "DeleteFlightFilter failed , no Connection" }
                cb.accept(false)
                _failcount++
            }
        }
    }

    /**
     * Insert records to Flight Filter Table
     * @param ff Vararg of FlightFilter to insert
     * @param cb Callback function with parameter (success: Boolean)
     */
    fun InsertFlightFilter(vararg ff: FlightFilter, cb: Consumer<Boolean>){
        Connect { success, connection ->
            if (success){
                try{
                    var updatecount : IntArray
                    connection!!.prepareStatement("INSERT INTO aas.tblFlightFilter (description, airlineCode, flightNumber, departureZone, arrivalZone) VALUES (?,?,?,?,?)").use { ps ->
                        ff.forEach { data ->
                            ps.setString(1, data.Description)
                            ps.setString(2, data.AirlineCode)
                            ps.setString(3, data.FlightNumber)
                            ps.setString(4, data.DepartureZone)
                            ps.setString(5, data.ArrivalZone)
                            ps.addBatch()
                        }
                        updatecount = ps.executeBatch()
                        ps.close()
                    }

                    connection.close()
                    Logger.info { "Inserted ${ff.size} records to AAS$aas_id Flight Filter Table, update counts: ${updatecount.joinToString()}" }
                    cb.accept(true)
                    _successcount++
                } catch (e : Exception){
                    Logger.error { "Failed to insert MySQL Adapter for AAS$aas_id Flight Filter: ${e.message}" }
                    cb.accept(false)
                    _failcount++
                }

            } else {
                Logger.error { "InsertFlightFilter failed , no Connection" }
                cb.accept(false)
                _failcount++
            }
        }

    }

}