package com.yourname.premiumah.storage;

import com.yourname.premiumah.model.ClaimReason;
import com.yourname.premiumah.model.ClaimRecord;
import com.yourname.premiumah.model.ClaimType;
import com.yourname.premiumah.model.Listing;
import com.yourname.premiumah.model.ListingStatus;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;

public final class StorageManager {
    private static final String LEGACY_IMPORT_KEY = "legacy_yaml_imported";

    private final JavaPlugin plugin;
    private File databaseFile;
    private File legacyYamlFile;
    private Connection connection;
    private final Map<String, Listing> listings = new LinkedHashMap<>();
    private final Map<String, ClaimRecord> claims = new LinkedHashMap<>();

    public StorageManager(JavaPlugin plugin) {
        this.plugin = plugin;
    }

    public synchronized void load() {
        plugin.getDataFolder().mkdirs();
        String databaseName = plugin.getConfig().getString("storage.sqlite-file", "auctionhouse.db");
        if (databaseName == null || databaseName.isBlank()) {
            databaseName = "auctionhouse.db";
        }
        this.databaseFile = new File(plugin.getDataFolder(), databaseName);
        this.legacyYamlFile = new File(plugin.getDataFolder(), "data.yml");

        listings.clear();
        claims.clear();
        try {
            connect();
            createSchema();
            loadListingsFromDatabase();
            loadClaimsFromDatabase();
            importLegacyYamlIfNeeded();
            plugin.getLogger().info("Loaded " + listings.size() + " listings and " + claims.size() + " claims from SQLite.");
        } catch (SQLException exception) {
            plugin.getLogger().severe("Failed to load SQLite storage: " + exception.getMessage());
        }
    }

    public synchronized void save() {
        try {
            connect();
            connection.setAutoCommit(false);
            try (Statement statement = connection.createStatement()) {
                statement.executeUpdate("DELETE FROM listings");
                statement.executeUpdate("DELETE FROM claims");
            }
            saveListings();
            saveClaims();
            connection.commit();
        } catch (SQLException | IOException exception) {
            rollbackQuietly();
            plugin.getLogger().severe("Failed to save SQLite storage: " + exception.getMessage());
        } finally {
            setAutoCommitQuietly(true);
        }
    }

    public synchronized void close() {
        if (connection == null) {
            return;
        }
        try {
            connection.close();
        } catch (SQLException exception) {
            plugin.getLogger().warning("Failed to close SQLite connection: " + exception.getMessage());
        } finally {
            connection = null;
        }
    }

    public synchronized Map<String, Listing> listingsSnapshot() {
        return new LinkedHashMap<>(listings);
    }

    public synchronized Map<String, ClaimRecord> claimsSnapshot() {
        return new LinkedHashMap<>(claims);
    }

    public synchronized void replaceListings(Map<String, Listing> replacement) {
        listings.clear();
        listings.putAll(replacement);
    }

    public synchronized void replaceClaims(Map<String, ClaimRecord> replacement) {
        claims.clear();
        claims.putAll(replacement);
    }

    private void connect() throws SQLException {
        if (connection != null && !connection.isClosed()) {
            return;
        }
        try {
            Class.forName("org.sqlite.JDBC");
        } catch (ClassNotFoundException exception) {
            throw new SQLException("SQLite JDBC driver is missing from the plugin jar.", exception);
        }
        connection = DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath());
        try (Statement statement = connection.createStatement()) {
            statement.execute("PRAGMA busy_timeout = 5000");
            statement.execute("PRAGMA foreign_keys = ON");
            statement.execute("PRAGMA journal_mode = WAL");
        }
    }

    private void createSchema() throws SQLException {
        try (Statement statement = connection.createStatement()) {
            statement.executeUpdate("""
                    CREATE TABLE IF NOT EXISTS metadata (
                        key TEXT PRIMARY KEY,
                        value TEXT NOT NULL
                    )
                    """);
            statement.executeUpdate("""
                    CREATE TABLE IF NOT EXISTS listings (
                        id TEXT PRIMARY KEY,
                        seller_uuid TEXT NOT NULL,
                        seller_name TEXT NOT NULL,
                        item TEXT NOT NULL,
                        price REAL NOT NULL,
                        created_at INTEGER NOT NULL,
                        expires_at INTEGER NOT NULL,
                        status TEXT NOT NULL,
                        buyer_uuid TEXT,
                        buyer_name TEXT,
                        updated_at INTEGER NOT NULL
                    )
                    """);
            statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_listings_status_expires ON listings(status, expires_at)");
            statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_listings_seller ON listings(seller_uuid, status)");
            statement.executeUpdate("""
                    CREATE TABLE IF NOT EXISTS claims (
                        id TEXT PRIMARY KEY,
                        owner_uuid TEXT NOT NULL,
                        owner_name TEXT NOT NULL,
                        listing_id TEXT,
                        type TEXT NOT NULL,
                        reason TEXT NOT NULL,
                        item TEXT,
                        money_amount REAL NOT NULL,
                        created_at INTEGER NOT NULL
                    )
                    """);
            statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_claims_owner ON claims(owner_uuid, created_at)");
            statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_claims_listing ON claims(listing_id)");
        }
    }

    private void loadListingsFromDatabase() throws SQLException {
        String sql = """
                SELECT id, seller_uuid, seller_name, item, price, created_at, expires_at, status,
                       buyer_uuid, buyer_name, updated_at
                FROM listings
                ORDER BY created_at DESC
                """;
        try (PreparedStatement statement = connection.prepareStatement(sql);
             ResultSet result = statement.executeQuery()) {
            while (result.next()) {
                String id = result.getString("id");
                try {
                    ItemStack item = ItemStackSerializer.fromBase64(result.getString("item"));
                    Listing listing = new Listing(
                            id,
                            UUID.fromString(result.getString("seller_uuid")),
                            result.getString("seller_name"),
                            item,
                            result.getDouble("price"),
                            result.getLong("created_at"),
                            result.getLong("expires_at"),
                            parseEnum(ListingStatus.class, result.getString("status"), ListingStatus.ACTIVE),
                            parseUuid(result.getString("buyer_uuid")),
                            result.getString("buyer_name"),
                            result.getLong("updated_at")
                    );
                    listings.put(id, listing);
                } catch (RuntimeException | IOException | ClassNotFoundException exception) {
                    plugin.getLogger().warning("Skipping invalid SQLite listing " + id + ": " + exception.getMessage());
                }
            }
        }
    }

    private void loadClaimsFromDatabase() throws SQLException {
        String sql = """
                SELECT id, owner_uuid, owner_name, listing_id, type, reason, item, money_amount, created_at
                FROM claims
                ORDER BY created_at DESC
                """;
        try (PreparedStatement statement = connection.prepareStatement(sql);
             ResultSet result = statement.executeQuery()) {
            while (result.next()) {
                String id = result.getString("id");
                try {
                    ClaimType type = parseEnum(ClaimType.class, result.getString("type"), ClaimType.ITEM);
                    ItemStack item = null;
                    String encodedItem = result.getString("item");
                    if (encodedItem != null && !encodedItem.isBlank()) {
                        item = ItemStackSerializer.fromBase64(encodedItem);
                    }
                    if (type == ClaimType.ITEM && item == null) {
                        plugin.getLogger().warning("Skipping SQLite item claim " + id + " because its item is missing.");
                        continue;
                    }
                    ClaimRecord claim = new ClaimRecord(
                            id,
                            UUID.fromString(result.getString("owner_uuid")),
                            result.getString("owner_name"),
                            result.getString("listing_id"),
                            type,
                            parseEnum(ClaimReason.class, result.getString("reason"), ClaimReason.EXPIRED_LISTING),
                            item,
                            result.getDouble("money_amount"),
                            result.getLong("created_at")
                    );
                    claims.put(id, claim);
                } catch (RuntimeException | IOException | ClassNotFoundException exception) {
                    plugin.getLogger().warning("Skipping invalid SQLite claim " + id + ": " + exception.getMessage());
                }
            }
        }
    }

    private void saveListings() throws SQLException, IOException {
        String sql = """
                INSERT INTO listings
                    (id, seller_uuid, seller_name, item, price, created_at, expires_at, status, buyer_uuid, buyer_name, updated_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """;
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            for (Listing listing : listings.values()) {
                statement.setString(1, listing.id());
                statement.setString(2, listing.sellerUuid().toString());
                statement.setString(3, listing.sellerName());
                statement.setString(4, ItemStackSerializer.toBase64(listing.rawItem()));
                statement.setDouble(5, listing.price());
                statement.setLong(6, listing.createdAt());
                statement.setLong(7, listing.expiresAt());
                statement.setString(8, listing.status().name());
                statement.setString(9, listing.buyerUuid() == null ? null : listing.buyerUuid().toString());
                statement.setString(10, listing.buyerName());
                statement.setLong(11, listing.updatedAt());
                statement.addBatch();
            }
            statement.executeBatch();
        }
    }

    private void saveClaims() throws SQLException, IOException {
        String sql = """
                INSERT INTO claims
                    (id, owner_uuid, owner_name, listing_id, type, reason, item, money_amount, created_at)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
                """;
        try (PreparedStatement statement = connection.prepareStatement(sql)) {
            for (ClaimRecord claim : claims.values()) {
                statement.setString(1, claim.id());
                statement.setString(2, claim.ownerUuid().toString());
                statement.setString(3, claim.ownerName());
                statement.setString(4, claim.listingId());
                statement.setString(5, claim.type().name());
                statement.setString(6, claim.reason().name());
                statement.setString(7, claim.rawItem() == null ? null : ItemStackSerializer.toBase64(claim.rawItem()));
                statement.setDouble(8, claim.moneyAmount());
                statement.setLong(9, claim.createdAt());
                statement.addBatch();
            }
            statement.executeBatch();
        }
    }

    private void importLegacyYamlIfNeeded() throws SQLException {
        if (!plugin.getConfig().getBoolean("storage.import-legacy-yaml", true)
                || !listings.isEmpty()
                || !claims.isEmpty()
                || getMetadata(LEGACY_IMPORT_KEY).equals("true")) {
            return;
        }
        if (legacyYamlFile == null || !legacyYamlFile.exists() || legacyYamlFile.length() == 0L) {
            return;
        }

        YamlConfiguration data = YamlConfiguration.loadConfiguration(legacyYamlFile);
        int beforeListings = listings.size();
        int beforeClaims = claims.size();
        loadLegacyListings(data.getConfigurationSection("listings"));
        loadLegacyClaims(data.getConfigurationSection("claims"));
        setMetadata(LEGACY_IMPORT_KEY, "true");

        int importedListings = listings.size() - beforeListings;
        int importedClaims = claims.size() - beforeClaims;
        if (importedListings > 0 || importedClaims > 0) {
            plugin.getLogger().info("Imported " + importedListings + " listings and " + importedClaims + " claims from legacy data.yml.");
            save();
        }
    }

    private void loadLegacyListings(ConfigurationSection root) {
        if (root == null) {
            return;
        }
        for (String id : root.getKeys(false)) {
            ConfigurationSection section = root.getConfigurationSection(id);
            if (section == null) {
                continue;
            }
            try {
                ItemStack item = section.getItemStack("item");
                if (item == null) {
                    plugin.getLogger().warning("Skipping legacy listing " + id + " because its item is missing.");
                    continue;
                }
                Listing listing = new Listing(
                        id,
                        UUID.fromString(section.getString("seller-uuid", "")),
                        section.getString("seller-name", "Unknown"),
                        item,
                        section.getDouble("price"),
                        section.getLong("created-at"),
                        section.getLong("expires-at"),
                        parseEnum(ListingStatus.class, section.getString("status"), ListingStatus.ACTIVE),
                        parseUuid(section.getString("buyer-uuid")),
                        section.getString("buyer-name", null),
                        section.getLong("updated-at", section.getLong("created-at"))
                );
                listings.put(id, listing);
            } catch (RuntimeException exception) {
                plugin.getLogger().warning("Skipping invalid legacy listing " + id + ": " + exception.getMessage());
            }
        }
    }

    private void loadLegacyClaims(ConfigurationSection root) {
        if (root == null) {
            return;
        }
        for (String id : root.getKeys(false)) {
            ConfigurationSection section = root.getConfigurationSection(id);
            if (section == null) {
                continue;
            }
            try {
                ClaimType type = parseEnum(ClaimType.class, section.getString("type"), ClaimType.ITEM);
                ItemStack item = section.getItemStack("item");
                if (type == ClaimType.ITEM && item == null) {
                    plugin.getLogger().warning("Skipping legacy item claim " + id + " because its item is missing.");
                    continue;
                }
                ClaimRecord claim = new ClaimRecord(
                        id,
                        UUID.fromString(section.getString("owner-uuid", "")),
                        section.getString("owner-name", "Unknown"),
                        section.getString("listing-id", null),
                        type,
                        parseEnum(ClaimReason.class, section.getString("reason"), ClaimReason.EXPIRED_LISTING),
                        item,
                        section.getDouble("money-amount"),
                        section.getLong("created-at")
                );
                claims.put(id, claim);
            } catch (RuntimeException exception) {
                plugin.getLogger().warning("Skipping invalid legacy claim " + id + ": " + exception.getMessage());
            }
        }
    }

    private String getMetadata(String key) throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement("SELECT value FROM metadata WHERE key = ?")) {
            statement.setString(1, key);
            try (ResultSet result = statement.executeQuery()) {
                if (result.next()) {
                    return result.getString("value");
                }
            }
        }
        return "";
    }

    private void setMetadata(String key, String value) throws SQLException {
        try (PreparedStatement statement = connection.prepareStatement("""
                INSERT INTO metadata(key, value)
                VALUES(?, ?)
                ON CONFLICT(key) DO UPDATE SET value = excluded.value
                """)) {
            statement.setString(1, key);
            statement.setString(2, value);
            statement.executeUpdate();
        }
    }

    private void rollbackQuietly() {
        try {
            if (connection != null) {
                connection.rollback();
            }
        } catch (SQLException ignored) {
        }
    }

    private void setAutoCommitQuietly(boolean autoCommit) {
        try {
            if (connection != null) {
                connection.setAutoCommit(autoCommit);
            }
        } catch (SQLException ignored) {
        }
    }

    private UUID parseUuid(String raw) {
        if (raw == null || raw.isBlank()) {
            return null;
        }
        return UUID.fromString(raw);
    }

    private <E extends Enum<E>> E parseEnum(Class<E> type, String raw, E fallback) {
        if (raw == null || raw.isBlank()) {
            return fallback;
        }
        try {
            return Enum.valueOf(type, raw);
        } catch (IllegalArgumentException ignored) {
            return fallback;
        }
    }
}
