// • ▌ ▄ ·.  ▄▄▄·  ▄▄ • ▪   ▄▄· ▄▄▄▄·  ▄▄▄·  ▐▄▄▄  ▄▄▄ .
// ·██ ▐███▪▐█ ▀█ ▐█ ▀ ▪██ ▐█ ▌▪▐█ ▀█▪▐█ ▀█ •█▌ ▐█▐▌·
// ▐█ ▌▐▌▐█·▄█▀▀█ ▄█ ▀█▄▐█·██ ▄▄▐█▀▀█▄▄█▀▀█ ▐█▐ ▐▌▐▀▀▀
// ██ ██▌▐█▌▐█ ▪▐▌▐█▄▪▐█▐█▌▐███▌██▄▪▐█▐█ ▪▐▌██▐ █▌▐█▄▄▌
// ▀▀  █▪▀▀▀ ▀  ▀ ·▀▀▀▀ ▀▀▀·▀▀▀ ·▀▀▀▀  ▀  ▀ ▀▀  █▪ ▀▀▀
//      Magicbane Emulator Project © 2013 - 2022
//                www.magicbane.com


package engine.db.archive;

import engine.Enum;
import engine.gameManager.DbManager;
import engine.objects.Bane;
import engine.objects.City;
import engine.workthreads.WarehousePushThread;
import org.joda.time.DateTime;
import org.pmw.tinylog.Logger;

import java.sql.*;
import java.time.LocalDateTime;
import java.util.concurrent.LinkedBlockingQueue;

import static engine.Enum.RecordEventType;

public class BaneRecord extends DataRecord {

    private static final LinkedBlockingQueue<BaneRecord> recordPool = new LinkedBlockingQueue<>();
    private RecordEventType eventType;
    private String cityHash;
    private String cityName;
    private String cityGuildHash;
    private String cityNationHash;
    private String baneDropperHash;
    private String baneGuildHash;
    private String baneNationHash;
    private DateTime baneLiveTime;
    private DateTime baneDropTime;

    private BaneRecord(Bane bane) {
        this.recordType = Enum.DataRecordType.BANE;
        this.eventType = RecordEventType.PENDING;
    }

    public static BaneRecord borrow(Bane bane, RecordEventType eventType) {
        BaneRecord baneRecord;

        baneRecord = recordPool.poll();

        if (baneRecord == null) {
            baneRecord = new BaneRecord(bane);
            baneRecord.eventType = eventType;
        } else {
            baneRecord.recordType = Enum.DataRecordType.BANE;
            baneRecord.eventType = eventType;

        }

        baneRecord.cityHash = bane.getCity().getHash();
        baneRecord.cityName = bane.getCity().getCityName();
        baneRecord.cityGuildHash = bane.getCity().getGuild().getHash();
        baneRecord.cityNationHash = bane.getCity().getGuild().getNation().getHash();


        if (bane.getOwner() == null) {
            baneRecord.baneDropperHash = "ERRANT";
            baneRecord.baneGuildHash = "ERRANT";
            baneRecord.baneNationHash = "ERRANT";
        } else {
            baneRecord.baneDropperHash = DataWarehouse.hasher.encrypt(bane.getOwner().getObjectUUID());  // getPlayerCharacter didn't check hash first?  OMFG


            baneRecord.baneGuildHash = bane.getOwner().getGuild().getHash();
            baneRecord.baneNationHash = bane.getOwner().getGuild().getNation().getHash();


            baneRecord.baneLiveTime = bane.getLiveDate();
            baneRecord.baneDropTime = bane.getPlacementDate();
        }


        return baneRecord;
    }

    public static PreparedStatement buildBanePushStatement(Connection connection, ResultSet rs) throws SQLException {

        PreparedStatement outStatement = null;
        String queryString = "INSERT INTO `warehouse_banehistory` (`event_number`, `city_id`, `city_name`, `char_id`, `offGuild_id`, `offNat_id`, `defGuild_id`, `defNat_id`, `dropDatetime`, `liveDateTime`, `resolution`) VALUES(?,?,?,?,?,?,?,?,?,?,?)";
        java.util.Date sqlDateTime;

        outStatement = connection.prepareStatement(queryString);

        // Bind record data

        outStatement.setInt(1, rs.getInt("event_number"));
        outStatement.setString(2, rs.getString("city_id"));
        outStatement.setString(3, rs.getString("city_name"));
        outStatement.setString(4, rs.getString("char_id"));
        outStatement.setString(5, rs.getString("offGuild_id"));
        outStatement.setString(6, rs.getString("offNat_id"));
        outStatement.setString(7, rs.getString("defGuild_id"));
        outStatement.setString(8, rs.getString("defNat_id"));

        sqlDateTime = rs.getTimestamp("dropDatetime");

        if (sqlDateTime == null)
            outStatement.setNull(9, Types.DATE);
        else
            outStatement.setTimestamp(9, rs.getTimestamp("dropDatetime"));

        sqlDateTime = rs.getTimestamp("dropDatetime");

        if (sqlDateTime == null)
            outStatement.setNull(10, Types.DATE);
        else
            outStatement.setTimestamp(10, rs.getTimestamp("liveDateTime"));

        outStatement.setString(11, rs.getString("resolution"));

        return outStatement;
    }

    public static PreparedStatement buildBaneQueryStatement(Connection connection) throws SQLException {

        PreparedStatement outStatement = null;
        String queryString = "SELECT * FROM `warehouse_banehistory` WHERE `event_number` > ?";
        outStatement = connection.prepareStatement(queryString);
        outStatement.setInt(1, WarehousePushThread.baneIndex);
        return outStatement;
    }

    public static DateTime getLastBaneDateTime(City city) {

        DateTime outDateTime = null;

        try (Connection connection = DbManager.getConnection();
             PreparedStatement statement = buildDateTimeQueryStatement(connection, city);
             ResultSet rs = statement.executeQuery()) {

            while (rs.next()) {

                outDateTime = new DateTime(rs.getTimestamp("endDatetime"));

            }

        } catch (SQLException e) {
            Logger.error(e.toString());
        }

        return outDateTime;
    }


    private static PreparedStatement buildDateTimeQueryStatement(Connection connection, City city) throws SQLException {
        PreparedStatement outStatement;
        String queryString = "SELECT `endDatetime` FROM `warehouse_banehistory` WHERE `city_id` = ? ORDER BY `endDatetime` DESC LIMIT 1";
        outStatement = connection.prepareStatement(queryString);
        outStatement.setString(1, city.getHash());
        return outStatement;

    }

    public static void updateLiveDate(Bane bane, DateTime dateTime) {

        if (bane == null)
            return;

        try (Connection connection = DbManager.getConnection();
             PreparedStatement statement = buildUpdateLiveDateStatement(connection, bane, dateTime)) {

            statement.execute();

        } catch (SQLException e) {
            Logger.error(e.toString());
        }
    }

    private static PreparedStatement buildUpdateLiveDateStatement(Connection connection, Bane bane, DateTime dateTime) throws SQLException {

        PreparedStatement outStatement = null;
        String queryString = "UPDATE `warehouse_banehistory` SET `liveDatetime` = ?, `dirty` = 1 WHERE `city_id` = ? AND `resolution` = 'PENDING'";

        outStatement = connection.prepareStatement(queryString);
        outStatement.setTimestamp(1, new java.sql.Timestamp(dateTime.getMillis()));
        outStatement.setString(2, bane.getCity().getHash());

        return outStatement;
    }

    private static PreparedStatement buildUpdateResolutionStatement(Connection connection, Bane bane, RecordEventType eventType) throws SQLException {

        PreparedStatement outStatement = null;
        String queryString = "UPDATE `warehouse_banehistory` SET `endDatetime` = ?, `resolution` = ?, `dirty` = 1 WHERE `city_id` = ? AND `resolution` = 'PENDING'";

        outStatement = connection.prepareStatement(queryString);
        outStatement.setTimestamp(1, Timestamp.valueOf(LocalDateTime.now()));
        outStatement.setString(2, eventType.name());
        outStatement.setString(3, bane.getCity().getHash());

        return outStatement;
    }

    public static void updateResolution(Bane bane, RecordEventType eventType) {

        try (Connection connection = DbManager.getConnection();
             PreparedStatement statement = buildUpdateResolutionStatement(connection, bane, eventType)) {

            statement.execute();

        } catch (SQLException e) {
            Logger.error(e.toString());
        }
    }

    public static String getBaneHistoryString() {

        String outString;
        String queryString;
        String dividerString;
        String newLine = System.getProperty("line.separator");
        outString = "[LUA_BANES() DATA WAREHOUSE]" + newLine;
        dividerString = "--------------------------------" + newLine;
        queryString = "CALL `baneHistory`()";

        try (Connection connection = DbManager.getConnection();
             PreparedStatement statement = connection.prepareCall(queryString);
             ResultSet rs = statement.executeQuery()) {

            while (rs.next()) {

                outString += "Magicbane unresolved banes: " + rs.getInt("PENDING") + '/' + rs.getInt("TOTAL") + newLine;
                outString += dividerString;
                outString += "Bane Resolution History" + newLine;
                outString += dividerString;

                outString += "Destruction: " + rs.getInt("DESTROY") + newLine;
                outString += "Capture: " + rs.getInt("CAPTURE") + newLine;
                outString += "Defended: " + rs.getInt("DEFEND") + newLine;
            }

        } catch (SQLException e) {
            e.printStackTrace();
        }
        return outString;
    }

    public static void updateDirtyRecords() {

        String queryString = "SELECT * FROM `warehouse_banehistory` where `dirty` = 1";

        // Reset character delta

        WarehousePushThread.baneDelta = 0;

        try (Connection localConnection = DbManager.getConnection();
             PreparedStatement statement = localConnection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE); // Make this an updatable result set as we'll reset the dirty flag as we go along
             ResultSet rs = statement.executeQuery()) {

            while (rs.next()) {

                // Only update the index and dirty flag
                // if the remote database update succeeded

                if (updateDirtyRecord(rs) == true)
                    WarehousePushThread.baneDelta++;
                else
                    continue;

                // Reset the dirty flag in the local database

                rs.updateInt("dirty", 0);
                rs.updateRow();
            }

        } catch (SQLException e) {
            Logger.error(e.toString());
        }
    }

    private static boolean updateDirtyRecord(ResultSet rs) {

        try (Connection remoteConnection = DataWarehouse.remoteConnectionPool.getConnection();
             PreparedStatement statement = buildUpdateDirtyStatement(remoteConnection, rs)) {

            statement.execute();
            return true;
        } catch (SQLException e) {
            Logger.error(e.toString());
            return false;
        }
    }

    private static PreparedStatement buildUpdateDirtyStatement(Connection connection, ResultSet rs) throws SQLException {

        PreparedStatement outStatement;
        String queryString = "UPDATE `warehouse_banehistory` SET `liveDateTime` = ?, `endDateTime` = ?, `resolution` = ? WHERE `event_number` = ?";
        java.util.Date sqlDateTime;

        outStatement = connection.prepareStatement(queryString);

        // Bind record data

        sqlDateTime = rs.getTimestamp("liveDateTime");

        if (sqlDateTime == null)
            outStatement.setNull(1, Types.DATE);
        else
            outStatement.setTimestamp(1, rs.getTimestamp("liveDateTime"));

        sqlDateTime = rs.getTimestamp("endDateTime");

        if (sqlDateTime == null)
            outStatement.setNull(2, Types.DATE);
        else
            outStatement.setTimestamp(2, rs.getTimestamp("endDateTime"));

        outStatement.setString(3, rs.getString("resolution"));
        outStatement.setInt(4, rs.getInt("event_number"));

        return outStatement;
    }

    void reset() {
        this.cityHash = null;
        this.cityGuildHash = null;
        this.cityNationHash = null;
        this.baneDropperHash = null;
        this.baneGuildHash = null;
        this.baneNationHash = null;
        this.baneLiveTime = null;
    }

    public void release() {
        this.reset();
        recordPool.add(this);
    }

    public void write() {

        try (Connection connection = DbManager.getConnection();
             PreparedStatement statement = buildBaneInsertStatement(connection)) {

            statement.execute();

        } catch (SQLException e) {
            Logger.error(e.toString());
        }

    }

    private PreparedStatement buildBaneInsertStatement(Connection connection) throws SQLException {

        PreparedStatement outStatement = null;
        String queryString = "INSERT INTO `warehouse_banehistory` (`city_id`, `city_name`, `char_id`, `offGuild_id`, `offNat_id`, `defGuild_id`, `defNat_id`, `dropDatetime`, `liveDateTime`, `resolution`) VALUES(?,?,?,?,?,?,?,?,?,?)";

        outStatement = connection.prepareStatement(queryString);

        outStatement.setString(1, this.cityHash);
        outStatement.setString(2, this.cityName);
        outStatement.setString(3, this.baneDropperHash);
        outStatement.setString(4, this.baneGuildHash);
        outStatement.setString(5, this.baneNationHash);
        outStatement.setString(6, this.cityGuildHash);
        outStatement.setString(7, this.cityNationHash);

        if (this.baneDropTime == null)
            outStatement.setNull(8, java.sql.Types.DATE);
        else
            outStatement.setTimestamp(8, new java.sql.Timestamp(this.baneDropTime.getMillis()));

        if (this.baneLiveTime == null)
            outStatement.setNull(9, java.sql.Types.DATE);
        else
            outStatement.setTimestamp(9, new java.sql.Timestamp(this.baneLiveTime.getMillis()));

        outStatement.setString(10, this.eventType.name());


        return outStatement;
    }
} // END CLASS