package database

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.mariadb.jdbc.Connection
import org.tinylog.Logger
import java.sql.DriverManager
import java.util.function.Consumer

/**
 * A class to manage a connection to a MariaDB database.
 *
 * @property address The address of the MariaDB server.
 * @property port The port number of the MariaDB server.
 * @property dbName The name of the database to connect to.
 * @property username The username for the database connection.
 * @property password The password for the database connection.
 */
@Suppress("unused")
class MariaDB (
    address : String = "localhost",
    port : Int = 3306,
    dbName : String = "aas",
    username : String = "admin",
    password : String = "admin"
) {
    private var connection : Connection? = null
    var connected : Boolean = false
    var SoundbankList : ArrayList<Soundbank> = ArrayList()
    var MessagebankList : ArrayList<Messagebank> = ArrayList()
    var LanguageLinkList : ArrayList<LanguageLink> = ArrayList()
    var SchedulebankList : ArrayList<ScheduleBank> = ArrayList()

    companion object {
        fun ValidDate(date: String): Boolean {
            // Check if the date is in the format DD/MM/YYYY
            val regex = Regex("""^\d{2}/\d{2}/\d{4}$""")
            return regex.matches(date)
        }
        fun ValidTime(time: String): Boolean {
            // Check if the time is in the format HH:MM:SS
            val regex = Regex("""^\d{2}:\d{2}:\d{2}$""")
            return regex.matches(time)
        }

        private val objectMapper = jacksonObjectMapper()
        /**
         * Convert SoundbankList, MessagebankList, LanguageLinkList, or SchedulebankList to a JSON String.
         * @param list The ArrayList to convert to a String.
         * @return A JSON String representation of the ArrayList.
         */
        fun <T> ArrayListtoString(list: ArrayList<T>) : String{
            return try {
                objectMapper.writeValueAsString(list.toArray())
            } catch (e: Exception) {
                Logger.error("Error converting list to JSON: ${e.message}" as Any)
                "[]"
            }

        }
    }

    init {
        try {
           connection = DriverManager.getConnection("jdbc:mariadb://$address:$port/$dbName", username, password) as Connection
            Logger.info("Connected to MariaDB" as Any)
            connected = true

            runBlocking {
                withContext(Dispatchers.IO){
                    Reload_Messagebank()
                    Reload_Soundbank()
                    Reload_LanguageLink()
                    Reload_Schedulebank()
                }
            }



            Logger.info { "Loading MariaDB completed"  }
            Logger.info { "Soundbank count: ${SoundbankList.size}"  }
            Logger.info { "Messagebank count: ${MessagebankList.size}"  }
            Logger.info { "LanguageLink count: ${LanguageLinkList.size}"  }
            Logger.info { "Schedulebank count: ${SchedulebankList.size}"  }


        } catch (e : Exception) {
            Logger.error("Failed to connect to MariaDB: ${e.message}" as Any)
        }
    }

    /**
     * Closes the MariaDB connection.
     */
    fun close() {
        try {
            connection?.close()

            Logger.info("Connection to MariaDB closed" as Any)
        } catch (e : Exception) {
            Logger.error("Error closing MariaDB connection: ${e.message}" as Any)
        }
        connected = false
    }

    /**
     * Get All Log from database
     * @param consumer A Consumer that will receive the list of logs
     */
    fun GetLog(consumer : Consumer<ArrayList<Log>>)  {
        val logList = ArrayList<Log>()
        try {
            val statement = connection?.createStatement()
            val resultSet = statement?.executeQuery("SELECT * FROM log")
            while (resultSet?.next() == true) {
                val log = Log(
                    resultSet.getLong("index").toULong(),
                    resultSet.getString("datenya"),
                    resultSet.getString("timenya"),
                    resultSet.getString("machine"),
                    resultSet.getString("description")
                )
                logList.add(log)
            }
        } catch (e : Exception) {
            Logger.error("Error fetching logs: ${e.message}" as Any)
        }
        consumer.accept(logList)
    }

    /**
     * Get Log from database by date
     * @param date The date to filter logs by (format: DD/MM/YYYY)
     * @param consumer A Consumer that will receive the list of logs for the specified date
     */
    fun GetLog(date : String, consumer: Consumer<ArrayList<Log>>) {
        val logList = ArrayList<Log>()
        if (ValidDate(date)){
            try {
                val statement = connection?.prepareStatement("SELECT * FROM log WHERE datenya = ?")
                statement?.setString(1, date)
                val resultSet = statement?.executeQuery()
                while (resultSet?.next() == true) {
                    val log = Log(
                        resultSet.getLong("index").toULong(),
                        resultSet.getString("datenya"),
                        resultSet.getString("timenya"),
                        resultSet.getString("machine"),
                        resultSet.getString("description")
                    )
                    logList.add(log)
                }
            } catch (e : Exception) {
                Logger.error("Error fetching logs for date $date: ${e.message}" as Any)
            }
        }

        consumer.accept(logList)
    }

    fun GetLog(date : String, filter : String, consumer: Consumer<ArrayList<Log>>) {
        val logList = ArrayList<Log>()
        if (ValidDate(date)){
            try {
                val statement = connection?.prepareStatement("SELECT * FROM log WHERE datenya = ? AND description LIKE ?")
                statement?.setString(1, date)
                statement?.setString(2, "%$filter%")
                val resultSet = statement?.executeQuery()
                while (resultSet?.next() == true) {
                    val log = Log(
                        resultSet.getLong("index").toULong(),
                        resultSet.getString("datenya"),
                        resultSet.getString("timenya"),
                        resultSet.getString("machine"),
                        resultSet.getString("description")
                    )
                    logList.add(log)
                }
            } catch (e : Exception) {
                Logger.error("Error fetching logs for date $date with filter $filter: ${e.message}" as Any)
            }
        }

        consumer.accept(logList)
    }

    /**
     * Reloads the ScheduleBank list from the database.
     */
    private fun Reload_Schedulebank() {
        SchedulebankList.clear()
        try {
            val statement = connection?.createStatement()
            val resultSet = statement?.executeQuery("SELECT * FROM schedulebank")
            while (resultSet?.next() == true) {
                val schedulebank = ScheduleBank(
                    resultSet.getLong("index").toUInt(),
                    resultSet.getString("Description"),
                    resultSet.getString("Day"),
                    resultSet.getString("Time"),
                    resultSet.getString("Soundpath"),
                    resultSet.getInt("Repeat").toUByte(),
                    resultSet.getBoolean("Enable"),
                    resultSet.getString("BroadcastZones"),
                    resultSet.getString("Language")
                )
                SchedulebankList.add(schedulebank)
            }
        } catch (e : Exception) {
            Logger.error("Error fetching schedulebanks: ${e.message}" as Any)
        }
    }

    /**
     * Reloads the language link list from the database.
     */
    private fun Reload_LanguageLink() {
        LanguageLinkList.clear()
        try {
            val statement = connection?.createStatement()
            val resultSet = statement?.executeQuery("SELECT * FROM languagelink")
            while (resultSet?.next() == true) {
                val languageLink = LanguageLink(
                    resultSet.getLong("index").toUInt(),
                    resultSet.getString("TAG"),
                    resultSet.getString("Language")
                )
                LanguageLinkList.add(languageLink)
            }
        } catch (e : Exception) {
            Logger.error("Error fetching language links: ${e.message}" as Any)
        }
    }

    /**
     * Reloads the soundbank list from the database.
     */
    private fun Reload_Soundbank() {

        SoundbankList.clear()
        try {
            val statement = connection?.createStatement()
            val resultSet = statement?.executeQuery("SELECT * FROM soundbank")
            while (resultSet?.next() == true) {
                val soundbank = Soundbank(
                    resultSet.getLong("index").toUInt(),
                    resultSet.getString("Description"),
                    resultSet.getString("TAG"),
                    resultSet.getString("Category"),
                    resultSet.getString("Language"),
                    resultSet.getString("VoiceType"),
                    resultSet.getString("Path")
                )
                SoundbankList.add(soundbank)
            }
        } catch (e : Exception) {
            Logger.error("Error fetching soundbanks: ${e.message}" as Any)
        }
    }

    /**
     * Reloads the messagebank list from the database.
     */
    private fun Reload_Messagebank() {
        MessagebankList.clear()
        try {
            val statement = connection?.createStatement()
            val resultSet = statement?.executeQuery("SELECT * FROM messagebank")
            while (resultSet?.next() == true) {
                val messagebank = Messagebank(
                    resultSet.getLong("index").toUInt(),
                    resultSet.getString("Description"),
                    resultSet.getString("Language"),
                    resultSet.getInt("ANN_ID").toUInt(),
                    resultSet.getString("Voice_Type"),
                    resultSet.getString("Message_Detail"),
                    resultSet.getString("Message_TAGS")
                )

                MessagebankList.add(messagebank)
            }
        } catch (e : Exception) {
            Logger.error("Error fetching messagebanks: ${e.message}" as Any)
        }
    }

}