package eu.mhsl.minecraft.pixelblocks;

import eu.mhsl.minecraft.pixelblocks.utils.Direction;
import eu.mhsl.minecraft.pixelblocks.pixelblock.PixelBlock;
import org.bukkit.Bukkit;
import org.bukkit.Location;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

public class PixelBlockDatabase {
    private final Connection db;

    private final PreparedStatement getAllPixelBlocks;
    private final PreparedStatement deletePixelBlock;
    private final PreparedStatement insertNewPixelBlock;
    private final PreparedStatement updateExistingPixelBlock;


    public PixelBlockDatabase(String url) {
        try {
            Class.forName("org.sqlite.JDBC");
            this.db = DriverManager.getConnection(url);

            this.db.createStatement().execute(
                    "CREATE TABLE IF NOT EXISTS pixelblocks (" +
                            "uuid CHAR(36) PRIMARY KEY, " +
                            "owner CHAR(36), " +
                            "locationWorldName CHAR(36), " +
                            "locationX DOUBLE, " +
                            "locationY DOUBLE, " +
                            "locationZ DOUBLE, " +
                            "entryLocationWorldName CHAR(36), " +
                            "entryLocationX DOUBLE, " +
                            "entryLocationY DOUBLE, " +
                            "entryLocationZ DOUBLE, " +
                            "direction CHAR(36)" +
                            ")"
            );

            this.deletePixelBlock = this.db.prepareStatement("DELETE FROM pixelblocks WHERE uuid = ?");
            this.getAllPixelBlocks = this.db.prepareStatement("SELECT * FROM pixelblocks");
            this.insertNewPixelBlock = this.db.prepareStatement(
            "INSERT INTO pixelblocks(uuid, owner, " +
                "locationWorldName, locationX, locationY, locationZ, " +
                "entryLocationWorldName, entryLocationX, entryLocationY, entryLocationZ, direction) " +
                "VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
            );
            this.updateExistingPixelBlock = this.db.prepareStatement(
            "UPDATE pixelblocks " +
                "SET owner=?, locationWorldName=?, locationX=?, locationY=?, locationZ=?," +
                "entryLocationWorldName=?, entryLocationX=?, entryLocationY=?, entryLocationZ=?, direction=? " +
                "WHERE uuid=?;"
            );
        } catch (SQLException | RuntimeException | ClassNotFoundException e) {
            throw new RuntimeException("Failed to load Database", e);
        }
    }

    public void close() throws SQLException {
        deletePixelBlock.close();
        getAllPixelBlocks.close();
        insertNewPixelBlock.close();
        updateExistingPixelBlock.close();
        db.close();
    }

    public void deletePixelBlock(PixelBlock pixelBlock) {
        Bukkit.getScheduler().runTaskAsynchronously(PixelBlocksPlugin.plugin, () -> {
            try {
                this.deletePixelBlock.setString(1, pixelBlock.blockUUID.toString());
                this.deletePixelBlock.executeUpdate();
                PixelBlocksPlugin.plugin.getLogger().info("DB: Deleted PixelBlock: " + pixelBlock.blockUUID);
            } catch (SQLException e) {
                throw new RuntimeException("Failed to delete PixelBlock", e);
            }
        });
    }

    public void savePixelBlock(PixelBlock pixelBlock) {
        Bukkit.getScheduler().runTask(PixelBlocksPlugin.plugin, () -> {
            List<UUID> storedPixelBlocks = new ArrayList<>();

            try {
                ResultSet pixelBlocksResult = this.getAllPixelBlocks.executeQuery();
                while (pixelBlocksResult.next()) {
                    storedPixelBlocks.add(UUID.fromString(pixelBlocksResult.getString("uuid")));
                }
            } catch (SQLException e) {
                throw new RuntimeException("Failed to fetch PixelBlock list", e);
            }

            if(!storedPixelBlocks.contains(pixelBlock.blockUUID)) {
                // create new entry if it does not exist
                try {
                    this.insertNewPixelBlock.setString(1, pixelBlock.blockUUID.toString());
                    this.insertNewPixelBlock.setString(2, pixelBlock.ownerUUID.toString());

                    this.insertNewPixelBlock.setString(3, pixelBlock.pixelBlockLocation.getWorld().getName());
                    this.insertNewPixelBlock.setDouble(4, pixelBlock.pixelBlockLocation.getX());
                    this.insertNewPixelBlock.setDouble(5, pixelBlock.pixelBlockLocation.getY());
                    this.insertNewPixelBlock.setDouble(6, pixelBlock.pixelBlockLocation.getZ());

                    if(pixelBlock.lastEntryLocation != null) {
                        this.insertNewPixelBlock.setString(7, pixelBlock.lastEntryLocation.getWorld().getName());
                        this.insertNewPixelBlock.setDouble(8, pixelBlock.lastEntryLocation.getX());
                        this.insertNewPixelBlock.setDouble(9, pixelBlock.lastEntryLocation.getY());
                        this.insertNewPixelBlock.setDouble(10, pixelBlock.lastEntryLocation.getZ());
                    } else {
                        this.insertNewPixelBlock.setString(7, Bukkit.getWorlds().getFirst().getName());
                        this.insertNewPixelBlock.setDouble(8, Bukkit.getWorlds().getFirst().getSpawnLocation().getX());
                        this.insertNewPixelBlock.setDouble(9, Bukkit.getWorlds().getFirst().getSpawnLocation().getY());
                        this.insertNewPixelBlock.setDouble(10, Bukkit.getWorlds().getFirst().getSpawnLocation().getZ());
                    }

                    this.insertNewPixelBlock.setString(11, pixelBlock.facingDirection.toString());

                    this.insertNewPixelBlock.executeUpdate();
                    PixelBlocksPlugin.plugin.getLogger().info("DB: Created PixelBlock: " + pixelBlock.blockUUID);
                } catch (SQLException e) {
                    throw new RuntimeException("Failed to save PixelBlock", e);
                }

            } else {
                // update existing entry
                try {
                    this.updateExistingPixelBlock.setString(1, pixelBlock.ownerUUID.toString());

                    this.updateExistingPixelBlock.setString(2, pixelBlock.pixelBlockLocation.getWorld().getName());
                    this.updateExistingPixelBlock.setDouble(3, pixelBlock.pixelBlockLocation.getX());
                    this.updateExistingPixelBlock.setDouble(4, pixelBlock.pixelBlockLocation.getY());
                    this.updateExistingPixelBlock.setDouble(5, pixelBlock.pixelBlockLocation.getZ());

                    if(pixelBlock.lastEntryLocation != null) {
                        this.updateExistingPixelBlock.setString(6, pixelBlock.lastEntryLocation.getWorld().getName());
                        this.updateExistingPixelBlock.setDouble(7, pixelBlock.lastEntryLocation.getX());
                        this.updateExistingPixelBlock.setDouble(8, pixelBlock.lastEntryLocation.getY());
                        this.updateExistingPixelBlock.setDouble(9, pixelBlock.lastEntryLocation.getZ());
                    } else {
                        this.updateExistingPixelBlock.setString(6, Bukkit.getWorlds().getFirst().getName());
                        this.updateExistingPixelBlock.setDouble(7, Bukkit.getWorlds().getFirst().getSpawnLocation().getX());
                        this.updateExistingPixelBlock.setDouble(8, Bukkit.getWorlds().getFirst().getSpawnLocation().getY());
                        this.updateExistingPixelBlock.setDouble(9, Bukkit.getWorlds().getFirst().getSpawnLocation().getZ());
                    }

                    this.updateExistingPixelBlock.setString(10, pixelBlock.blockUUID.toString());
                    this.updateExistingPixelBlock.setString(11, pixelBlock.facingDirection.toString());

                    this.updateExistingPixelBlock.executeUpdate();
                    PixelBlocksPlugin.plugin.getLogger().info("DB: Updated PixelBlock: " + pixelBlock.blockUUID);
                } catch (SQLException e) {
                    throw new RuntimeException("Failed updating PixelBlock", e);
                }
            }
        });
    }

    public void loadPixelBlocks() {
        try {
            ResultSet allPixelBlocks = this.getAllPixelBlocks.executeQuery();

            while (allPixelBlocks.next()) {
                Location blockLocation = new Location(
                    Bukkit.getWorld(allPixelBlocks.getString("locationWorldName")),
                    allPixelBlocks.getDouble("locationX"),
                    allPixelBlocks.getDouble("locationY"),
                    allPixelBlocks.getDouble("locationZ")
                );

                Location entryLocation = new Location(
                    Bukkit.getWorld(allPixelBlocks.getString("entryLocationWorldName")),
                    allPixelBlocks.getDouble("entryLocationX"),
                    allPixelBlocks.getDouble("entryLocationY"),
                    allPixelBlocks.getDouble("entryLocationZ")
                );

                PixelBlock block = new PixelBlock(
                    blockLocation,
                    UUID.fromString(allPixelBlocks.getString("owner")),
                    UUID.fromString(allPixelBlocks.getString("uuid")),
                    Direction.valueOf(allPixelBlocks.getString("direction"))
                );
                block.setLastEntryLocation(entryLocation);
                PixelBlocksPlugin.plugin.getLogger().info("DB: Loaded PixelBlock: " + block.blockUUID);
            }
        } catch (SQLException e) {
            throw new RuntimeException("Failed loading PixelBlocks", e);
        }
    }
}