package moe.oko.opennaw.storage;

import moe.oko.opennaw.OpenNAW;
import moe.oko.opennaw.model.City;
import moe.oko.opennaw.model.Gate;
import moe.oko.opennaw.model.GateBlock;
import moe.oko.opennaw.model.Nation;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.*;

public class NAWDatabase {

    private Connection connection; //SQL Conn instance

    private final String INITIALIZE_NATION_PLAYERS = "CREATE TABLE IF NOT EXISTS `nation_players`" +
            "(`nation_name` VARCHAR(50) NOT NULL COMMENT 'The name of the nation for the player to join'," +
            "`player_uuid` VARCHAR(50) NOT NULL COMMENT 'The UUID of the player who is in that nation', PRIMARY KEY (`player_uuid`));";
    private final String INITIALIZE_NATIONS = "CREATE TABLE IF NOT EXISTS `nations` (" +
            "`nation_name` VARCHAR(50) NOT NULL COMMENT 'Nation name', PRIMARY KEY (`nation_name`)," +
            "`groupName` VARCHAR(50) NOT NULL COMMENT 'Name for the luckperms group.', " +
            "`dynmapIcon` VARCHAR(50) NOT NULL COMMENT 'National Icon for Dynmap'," +
            "`colour` VARCHAR(50) NOT NULL COMMENT 'Colour for the nation'," +
            "`spawnworld` VARCHAR(50) NOT NULL," +
            "`spawnx` INT NOT NULL DEFAULT 0," +
            "`spawny` INT NOT NULL DEFAULT 0," +
            "`spawnz` INT NOT NULL DEFAULT 0);";
    private final String INITIALIZE_CITIES = "CREATE TABLE IF NOT EXISTS `cities` (" +
            "`city_name` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'City Name', PRIMARY KEY (`city_name`)," +
            "`resource` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'Resource the city provides'," +
            "`type` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'The type of city'," +
            "`owner` VARCHAR(50) NOT NULL DEFAULT '' COMMENT 'The name of the nation that owns it'," +
            "`pointworld` VARCHAR(50) NOT NULL DEFAULT ''," +
            "`pointx` INT NOT NULL DEFAULT 0," +
            "`pointy` INT NOT NULL DEFAULT 0," +
            "`pointz` INT NOT NULL DEFAULT 0) COMMENT='Cities on the NAW map';";

    private final String INITIALIZE_GATES = "CREATE TABLE IF NOT EXISTS `gates` (" +
            "`name` VARCHAR(50) NOT NULL,`uuid` VARCHAR(50) NOT NULL, PRIMARY KEY (`uuid`));";

    private final String INITIALITE_GATE_SIGNS = "CREATE TABLE IF NOT EXISTS `gateSigns`(" +
            "`uuid` VARCHAR(50) NOT NULL, `id` VARCHAR(50) NOT NULL, `world` VARCHAR(50) NOT NULL, `x` INT NOT NULL, `y` INT NOT NULL, `z` INT NOT NULL, PRIMARY KEY (`id`));";

    private final String INITIALIZE_GATE_BLOCKS = "CREATE TABLE IF NOT EXISTS `gateBlocks`(" +
            "`uuid` VARCHAR(50) NOT NULL, `id` VARCHAR(50) NOT NULL, `world` VARCHAR(50) NOT NULL, `x` INT NOT NULL, `y` INT NOT NULL, `z` INT NOT NULL, PRIMARY KEY (`id`));";

    private final String SELECT_CITIES = "SELECT * FROM `cities`;";
    private final String SELECT_NATION = "SELECT * FROM `nations`;";
    private final String SELECT_PLAYERS = "SELECT * FROM `nation_players`;";

    private final String SELECT_GATES = "SELECT * FROM `gates`;";

    private final String SELECT_GATE_SIGNS = "SELECT * FROM `gateSigns`;";

    private final String SELECT_GATE_BLOCKS = "SELECT * FROM `gateBlocks`;";

    //name 1,9; group 2,10; icon 3,11; colour 4, 12; world 5,13; x 6,14; y 7,15; z 8,16;
    private final String SAVE_NATION = "INSERT INTO `nations`(nation_name,groupName,dynmapIcon, colour, spawnworld,spawnx,spawny,spawnz) VALUES " +
            "(?,?,?,?,?,?,?,?)" +
            "ON DUPLICATE KEY UPDATE nation_name=?, groupName=?, dynmapIcon=?, colour=?, spawnworld=?, spawnx=?, spawny=?, spawnz=?;";
    //MAKE SURE YOU RUN AS BATCH (nation, 1,3) (playeruuid, 2,4);
    private final String SAVE_PLAYER = "INSERT INTO `nation_players`(nation_name, player_uuid) VALUES (?,?) " +
            "ON DUPLICATE KEY UPDATE nation_name=?, player_uuid=?;";
    //name 1,9; resource 2,10; type 3,11; owner 4,12; world 5,13; x 6,14; y 7,15; z 8,16;
    private final String SAVE_CITY = "INSERT INTO `cities`(city_name, resource, type, OWNER, pointworld, pointx, pointy, pointz) VALUES (?,?,?,?,?,?,?,?) " +
            "ON DUPLICATE KEY UPDATE city_name=?, resource=?, type=?, OWNER=?, pointworld=?, pointx=?, pointy=?, pointz=?;";

    //name, 1 3; uuid 2 4;
    private final String SAVE_GATE = "INSERT INTO `gates`(name, uuid) VALUES (?,?) " +
            "ON DUPLICATE KEY UPDATE name=?, uuid=?;";

    //uuid 1, 7; id 2, 8; world 3, 9; x 4 10; y 5 11; z 6 12;
    private final String SAVE_GATE_SIGN = "INSERT INTO `gateSigns`(uuid, id, world, x, y, z) VALUES (?,?,?,?,?,?)" +
            "ON DUPLICATE KEY UPDATE uuid=?, id=?, world=?, x=?, y=?, z=?;";

    private final String SAVE_GATE_BLOCK = "INSERT INTO `gateBlocks`(uuid, id, world, x, y, z) VALUES (?,?,?,?,?,?) " +
            "ON DUPLICATE KEY UPDATE uuid=?, id=?, world=?, x=?, y=?, z=?;";

    public NAWDatabase(String username, String password, String host, int port, String database) {
        //all relevant
        try {
            connection = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + database, username, password);
        } catch (Exception ex) {
            ex.printStackTrace();
            OpenNAW.getInstance().getLogger().severe("FAILED TO INITIALIZE PERSISTENT MYSQL DATABASE.");
        }
        //Unfugg Tables on startup
        initializeTables();
    }

    private void initializeTables() {
        try {
            OpenNAW.getInstance().getLogger().info("Table initialization start.");
            PreparedStatement nationPlayersInit = connection.prepareStatement(INITIALIZE_NATION_PLAYERS);
            nationPlayersInit.execute();
            PreparedStatement nationsInit = connection.prepareStatement(INITIALIZE_NATIONS);
            nationsInit.execute();
            PreparedStatement cityInit = connection.prepareStatement(INITIALIZE_CITIES);
            cityInit.execute();
            PreparedStatement gatesInit = connection.prepareStatement(INITIALIZE_GATES);
            gatesInit.execute();
            PreparedStatement gatesInit2 = connection.prepareStatement(INITIALITE_GATE_SIGNS);
            gatesInit2.execute();
            PreparedStatement gatesInit3 = connection.prepareStatement(INITIALIZE_GATE_BLOCKS);
            gatesInit3.execute();
        } catch (Exception ex) {
            ex.printStackTrace();
            OpenNAW.getInstance().getLogger().severe("Failed to initialize tables for sql.");
        }
    }

    public List<City> loadCities() {
        try {
            OpenNAW.getInstance().getLogger().info("Loading Cities...");
            PreparedStatement pullCity = connection.prepareStatement(SELECT_CITIES);
            ResultSet rs = pullCity.executeQuery();
            List<City> cityLoad = new ArrayList<>();
            var i = 0;
            while(rs.next()) {
                var name = rs.getString(1);
                var resource = rs.getString(2);
                var type = rs.getString(3);
                var nation = OpenNAW.getInstance().getNationHandler().getNationByName(rs.getString(4));
                var loc = new Location(Bukkit.getWorld(rs.getString(5)), rs.getInt(6), rs.getInt(7), rs.getInt(8));
                cityLoad.add(new City(name, resource, type, loc, nation));
                i++;
            }
            OpenNAW.getInstance().getLogger().info(i + " cities loaded.");
            return cityLoad;
        } catch (Exception ex) {
            ex.printStackTrace();
            OpenNAW.getInstance().getLogger().severe("Failed to pull cities from db.");
            return null;
        }
    }

    public List<Nation> loadNations() {
        try {
            OpenNAW.getInstance().getLogger().info("Loading Nations...");
            PreparedStatement pullNation = connection.prepareStatement(SELECT_NATION);
            ResultSet rs = pullNation.executeQuery();
            List<Nation> nationLoad = new ArrayList<>();
            var i = 0;
            while(rs.next()) {
                var name = rs.getString(1);
                var group = OpenNAW.getInstance().getGroupHandler().getGroupFromString(rs.getString(2));
                var icon = rs.getString(3);
                var colour = rs.getString(4);
                var location = new Location(Bukkit.getWorld(rs.getString(5)), rs.getInt(6), rs.getInt(7), rs.getInt(8));
                nationLoad.add(new Nation(name, group, icon, colour, location));
                i++;
            }
            OpenNAW.getInstance().getLogger().info(i + " nations loaded.");
            return nationLoad;
        } catch (Exception ex) {
            ex.printStackTrace();
            OpenNAW.getInstance().getLogger().severe("Failed to pull nations from db.");
            return null;
        }
    }

    public void loadPlayers() {
        try {
            PreparedStatement pullPlayers = connection.prepareStatement(SELECT_PLAYERS);
            ResultSet rs = pullPlayers.executeQuery();
            OpenNAW.getInstance().getLogger().info("Loading Players...");
            var i = 0;
            while(rs.next()) {
                OpenNAW.getInstance().getNationHandler().insertPlayerIntoNation(UUID.fromString(rs.getString(2)), rs.getString(1));
                i++;
            }
            OpenNAW.getInstance().getLogger().info(i + " players loaded.");
        } catch (Exception ex) {
            ex.printStackTrace();
            OpenNAW.getInstance().getLogger().severe("Failed to load players.");
        }
    }

    public void saveNations(Collection<Nation> nations) {
        try {
            OpenNAW.getInstance().getLogger().info("Starting nation save...");
            //name 1,9; group 2,10; icon 3,11; colour 4, 12; world 5,13; x 6,14; y 7,15; z 8,16;
            PreparedStatement nationSave = connection.prepareStatement(SAVE_NATION);
            int i = 0;
            for(Nation nation : nations) {
                nationSave.setString(1, nation.getName());
                nationSave.setString(9, nation.getName());
                nationSave.setString(2, nation.getGroup().getName());
                nationSave.setString(10, nation.getGroup().getName());
                nationSave.setString(3, nation.getDynmapIcon());
                nationSave.setString(11, nation.getDynmapIcon());
                nationSave.setString(4, nation.getColour());
                nationSave.setString(12, nation.getColour());
                var loc = nation.getSpawn();
                nationSave.setString(5, loc.getWorld().getName());
                nationSave.setString(13, loc.getWorld().getName());
                nationSave.setInt(6, loc.getBlockX());
                nationSave.setInt(14, loc.getBlockX());
                nationSave.setInt(7, loc.getBlockY());
                nationSave.setInt(15, loc.getBlockY());
                nationSave.setInt(8, loc.getBlockZ());
                nationSave.setInt(16, loc.getBlockZ());

                //Values are set, this is autistic. Run.
                nationSave.addBatch();
                i++;

                PreparedStatement playersSave = connection.prepareStatement(SAVE_PLAYER);
                //MAKE SURE YOU RUN AS BATCH (nation, 1,3) (playeruuid, 2,4);
                int pc = 0;
                for(OfflinePlayer nationPlayer : nation.getPlayerMap().values()) {
                    //Oh nnononono
                    playersSave.setString(1, nation.getName());
                    playersSave.setString(3, nation.getName());
                    playersSave.setString(2, nationPlayer.getUniqueId().toString());
                    playersSave.setString(4, nationPlayer.getUniqueId().toString());
                    playersSave.addBatch();
                    pc++;
                }
                //Run all at once to avoid ratelimit.
                playersSave.executeBatch();
                OpenNAW.getInstance().getLogger().info("Saved " + pc + " players for " + nation.getName() + ".");
            }
            nationSave.executeBatch();
            OpenNAW.getInstance().getLogger().info("Saved " + i + " nations.");
        } catch (Exception ex) {
            ex.printStackTrace();
            OpenNAW.getInstance().getLogger().severe("Failed to save nations.");
        }
    }

    public void saveCities(Collection<City> cities) {
        try {
            PreparedStatement citySave = connection.prepareStatement(SAVE_CITY);
            int i = 0;
            for(City city : cities) {
                //name 1,9; resource 2,10; type 3,11; owner 4,12; world 5,13; x 6,14; y 7,15; z 8,16;
                citySave.setString(1, city.getName());
                citySave.setString(9, city.getName());
                citySave.setString(2, city.getResource());
                citySave.setString(10, city.getResource());
                citySave.setString(3, city.getType().name());
                citySave.setString(11, city.getType().name());
                citySave.setString(4, city.getOwner().getName());
                citySave.setString(12, city.getOwner().getName());
                var loc = city.getPoint();
                citySave.setString(5, loc.getWorld().getName());
                citySave.setString(13, loc.getWorld().getName());
                citySave.setInt(6, loc.getBlockX());
                citySave.setInt(14, loc.getBlockX());
                citySave.setInt(7, loc.getBlockY());
                citySave.setInt(15, loc.getBlockY());
                citySave.setInt(8, loc.getBlockZ());
                citySave.setInt(16, loc.getBlockZ());
                citySave.addBatch();
                i++;
            }
            citySave.executeBatch();
            OpenNAW.getInstance().getLogger().info("Saved " + i + " cities.");
        } catch (Exception ex) {
          ex.printStackTrace();
          OpenNAW.getInstance().getLogger().severe("Failed to save cities.");
        }
    }

    public void loadGates() {
        try {
            List<Gate> gateLoad = new ArrayList<>();
            PreparedStatement gateload = connection.prepareStatement(SELECT_GATES);
            ResultSet rs = gateload.executeQuery();
            int i = 0;
            while(rs.next()) {
                var newGate = new Gate(UUID.fromString(rs.getString(2)), rs.getString(1));
                OpenNAW.getInstance().getGateHandler().getGates().put(newGate.getUuid(), newGate);
                i++;
            }
            OpenNAW.getInstance().getLogger().info("Loaded " + i + " gates.");
            PreparedStatement gateSignLoad = connection.prepareStatement(SELECT_GATE_SIGNS);
            ResultSet rs2 = gateSignLoad.executeQuery();
            i = 0;
            while(rs2.next()) {
                var gate = OpenNAW.getInstance().getGateHandler().getGateByUuid(UUID.fromString(rs2.getString(1)));
                //uuid 1, 7; id 2, 8; world 3, 9; x 4 10; y 5 11; z 6 12;
                gate.registerSignFromDB(new GateBlock(UUID.fromString(rs2.getString(2)),
                        new Location(Bukkit.getWorld(rs2.getString(3)), rs2.getInt(4)
                                , rs2.getInt(5), rs2.getInt(6)).getBlock()));
                i++;
            }
            OpenNAW.getInstance().getLogger().info("Loaded " + i + " signs for gates.");
            PreparedStatement gateBlockLoad = connection.prepareStatement(SELECT_GATE_BLOCKS);
            ResultSet rs3 = gateBlockLoad.executeQuery();
            i = 0;
            while(rs3.next()) {
                var gate = OpenNAW.getInstance().getGateHandler().getGateByUuid(UUID.fromString(rs3.getString(1)));
                gate.registerGateBlockDb(new GateBlock(UUID.fromString(rs3.getString(2)),
                        new Location(Bukkit.getWorld(rs3.getString(3)), rs3.getInt(4)
                                , rs3.getInt(5), rs3.getInt(6)).getBlock()));
                i++;
            }
            OpenNAW.getInstance().getLogger().info("Loaded " + i + " blocks for gates.");
        } catch (Exception ex) {
            ex.printStackTrace();
            OpenNAW.getInstance().getLogger().severe("Failed to save gates.");
        }
    }

    public void saveGates(Collection<Gate> gates) {
        try {
            OpenNAW.getInstance().getLogger().info("Starting gate save.");
            //name, 1 3; uid 2, 4;
            //for signs and blocks uuid 1, 7; id 2, 8; world 3, 9; x 4 10; y 5 11; z 6 12;
            PreparedStatement gateSave = connection.prepareStatement(SAVE_GATE);
            PreparedStatement gateSignSave = connection.prepareStatement(SAVE_GATE_SIGN);
            PreparedStatement gateBlockSave = connection.prepareStatement(SAVE_GATE_BLOCK);
            int g = 0;
            for(var gate : gates) {
                int i = 0;
                gateSave.setString(1, gate.getName());
                gateSave.setString(3, gate.getName());
                gateSave.setString(2, gate.getUuid().toString());
                gateSave.setString(4, gate.getUuid().toString());
                gateSave.execute();
                for(var sign : gate.getSigns().values()) {
                    //sign save
                    gateSignSave.setString(1, gate.getUuid().toString()); //UUID for gate identification (so we know where
                    gateSignSave.setString(7, gate.getUuid().toString()); //to put blocks on load).
                    gateSignSave.setString(2, sign.getId().toString()); //Id's to prevent duplicates in the DB
                    gateSignSave.setString(8, sign.getId().toString());
                    var loc = sign.getBlock().getLocation();
                    gateSignSave.setString(3, loc.getWorld().getName());
                    gateSignSave.setString(9, loc.getWorld().getName());
                    gateSignSave.setInt(4, loc.getBlockX());
                    gateSignSave.setInt(10, loc.getBlockX());
                    gateSignSave.setInt(5, loc.getBlockY());
                    gateSignSave.setInt(11, loc.getBlockY());
                    gateSignSave.setInt(6, loc.getBlockZ());
                    gateSignSave.setInt(12, loc.getBlockZ());
                    gateSignSave.addBatch();
                    i++;
                }
                gateSignSave.executeBatch();
                OpenNAW.getInstance().getLogger().info("Saved " + i + " signs for gate " + gate.getName());
                i = 0;
                //Now we save all blocks
                for(var block : gate.getGateBlocks().keySet()) {
                    //sign save
                    gateBlockSave.setString(1, gate.getUuid().toString()); //UUID for gate identification (so we know where
                    gateBlockSave.setString(7, gate.getUuid().toString()); //to put blocks on load).
                    gateBlockSave.setString(2, block.getId().toString()); //Id's to prevent duplicates in the DB
                    gateBlockSave.setString(8, block.getId().toString());
                    var loc = block.getBlock().getLocation();
                    gateBlockSave.setString(3, loc.getWorld().getName());
                    gateBlockSave.setString(9, loc.getWorld().getName());
                    gateBlockSave.setInt(4, loc.getBlockX());
                    gateBlockSave.setInt(10, loc.getBlockX());
                    gateBlockSave.setInt(5, loc.getBlockY());
                    gateBlockSave.setInt(11, loc.getBlockY());
                    gateBlockSave.setInt(6, loc.getBlockZ());
                    gateBlockSave.setInt(12, loc.getBlockZ());
                    gateBlockSave.addBatch();
                    i++;
                }
                gateBlockSave.executeBatch();
                OpenNAW.getInstance().getLogger().info("Saved " + i + " blocks for gate " + gate.getName());
                g++;
            }
            OpenNAW.getInstance().getLogger().info("Saved " + g + " gates.");
        } catch (Exception ex) {
            ex.printStackTrace();
            OpenNAW.getInstance().getLogger().severe("Failed to save gates.");
        }
    }
}
