package com.bitnix.dirtcryptostore.storage;

import com.bitnix.dirtcryptostore.DirtCryptoStorePlugin;
import com.bitnix.dirtcryptostore.model.PendingInvoice;

import java.io.File;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.UUID;

public class SQLiteStorage {

    private final DirtCryptoStorePlugin plugin;
    private final File databaseFile;

    public SQLiteStorage(DirtCryptoStorePlugin plugin) {
        this.plugin = plugin;
        this.databaseFile = new File(plugin.getDataFolder(),
                plugin.getConfig().getString("settings.storage.sqlite-file", "dirtcryptostore.db"));
    }

    public void init() {
        if (!plugin.getDataFolder().exists()) {
            plugin.getDataFolder().mkdirs();
        }

        try (Connection connection = getConnection();
             Statement statement = connection.createStatement()) {

            statement.executeUpdate("""
                    CREATE TABLE IF NOT EXISTS invoices (
                        invoice_id TEXT PRIMARY KEY,
                        player_uuid TEXT NOT NULL,
                        player_name TEXT NOT NULL,
                        product_id TEXT NOT NULL,
                        wallet_id TEXT NOT NULL,
                        wallet_address TEXT NOT NULL,
                        amount_sats INTEGER NOT NULL,
                        created_at INTEGER NOT NULL,
                        expires_at INTEGER NOT NULL,
                        status TEXT NOT NULL,
                        txid TEXT NOT NULL,
                        confirmations INTEGER NOT NULL,
                        fulfilled INTEGER NOT NULL,
                        matched_at INTEGER NOT NULL,
                        fulfilled_at INTEGER NOT NULL,
                        notes TEXT NOT NULL
                    )
                    """);

            statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_invoices_player_uuid ON invoices(player_uuid)");
            statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_invoices_status ON invoices(status)");
            statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_invoices_txid ON invoices(txid)");
            statement.executeUpdate("CREATE INDEX IF NOT EXISTS idx_invoices_created_at ON invoices(created_at)");
        } catch (SQLException e) {
            plugin.getLogger().severe("Failed to initialize SQLite storage: " + e.getMessage());
        }
    }

    public List<PendingInvoice> loadAllInvoices() {
        List<PendingInvoice> invoices = new ArrayList<>();

        try (Connection connection = getConnection();
             PreparedStatement statement = connection.prepareStatement("SELECT * FROM invoices");
             ResultSet rs = statement.executeQuery()) {

            while (rs.next()) {
                invoices.add(new PendingInvoice(
                        rs.getString("invoice_id"),
                        UUID.fromString(rs.getString("player_uuid")),
                        rs.getString("player_name"),
                        rs.getString("product_id"),
                        rs.getString("wallet_id"),
                        rs.getString("wallet_address"),
                        rs.getLong("amount_sats"),
                        rs.getLong("created_at"),
                        rs.getLong("expires_at"),
                        parseStatus(rs.getString("status")),
                        rs.getString("txid"),
                        rs.getInt("confirmations"),
                        rs.getInt("fulfilled") == 1,
                        rs.getLong("matched_at"),
                        rs.getLong("fulfilled_at"),
                        rs.getString("notes")
                ));
            }
        } catch (Exception e) {
            plugin.getLogger().severe("Failed to load invoices from SQLite: " + e.getMessage());
        }

        return invoices;
    }

    public void saveInvoice(PendingInvoice invoice) {
        String sql = """
                INSERT INTO invoices (
                    invoice_id, player_uuid, player_name, product_id, wallet_id, wallet_address,
                    amount_sats, created_at, expires_at, status, txid, confirmations,
                    fulfilled, matched_at, fulfilled_at, notes
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                ON CONFLICT(invoice_id) DO UPDATE SET
                    player_uuid=excluded.player_uuid,
                    player_name=excluded.player_name,
                    product_id=excluded.product_id,
                    wallet_id=excluded.wallet_id,
                    wallet_address=excluded.wallet_address,
                    amount_sats=excluded.amount_sats,
                    created_at=excluded.created_at,
                    expires_at=excluded.expires_at,
                    status=excluded.status,
                    txid=excluded.txid,
                    confirmations=excluded.confirmations,
                    fulfilled=excluded.fulfilled,
                    matched_at=excluded.matched_at,
                    fulfilled_at=excluded.fulfilled_at,
                    notes=excluded.notes
                """;

        try (Connection connection = getConnection();
             PreparedStatement ps = connection.prepareStatement(sql)) {

            ps.setString(1, invoice.getInvoiceId());
            ps.setString(2, invoice.getPlayerUuid().toString());
            ps.setString(3, invoice.getPlayerName());
            ps.setString(4, invoice.getProductId());
            ps.setString(5, invoice.getWalletId());
            ps.setString(6, invoice.getWalletAddress());
            ps.setLong(7, invoice.getAmountSats());
            ps.setLong(8, invoice.getCreatedAt());
            ps.setLong(9, invoice.getExpiresAt());
            ps.setString(10, invoice.getStatus().name());
            ps.setString(11, invoice.getTxid());
            ps.setInt(12, invoice.getConfirmations());
            ps.setInt(13, invoice.isFulfilled() ? 1 : 0);
            ps.setLong(14, invoice.getMatchedAt());
            ps.setLong(15, invoice.getFulfilledAt());
            ps.setString(16, invoice.getNotes());
            ps.executeUpdate();

        } catch (SQLException e) {
            plugin.getLogger().severe("Failed to save invoice " + invoice.getInvoiceId() + " to SQLite: " + e.getMessage());
        }
    }

    public void deleteInvoice(String invoiceId) {
        try (Connection connection = getConnection();
             PreparedStatement ps = connection.prepareStatement("DELETE FROM invoices WHERE invoice_id = ?")) {
            ps.setString(1, invoiceId);
            ps.executeUpdate();
        } catch (SQLException e) {
            plugin.getLogger().severe("Failed to delete invoice " + invoiceId + " from SQLite: " + e.getMessage());
        }
    }

    public int pruneOldInvoices(long cutoffTimeMillis, List<String> statuses) {
        if (statuses == null || statuses.isEmpty()) {
            return 0;
        }

        StringBuilder placeholders = new StringBuilder();
        for (int i = 0; i < statuses.size(); i++) {
            if (i > 0) placeholders.append(",");
            placeholders.append("?");
        }

        String sql = "DELETE FROM invoices WHERE created_at < ? AND status IN (" + placeholders + ")";

        try (Connection connection = getConnection();
             PreparedStatement ps = connection.prepareStatement(sql)) {

            ps.setLong(1, cutoffTimeMillis);
            for (int i = 0; i < statuses.size(); i++) {
                ps.setString(i + 2, statuses.get(i).toUpperCase(Locale.ROOT));
            }

            return ps.executeUpdate();
        } catch (SQLException e) {
            plugin.getLogger().severe("Failed to prune old invoices from SQLite: " + e.getMessage());
            return 0;
        }
    }

    private Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:sqlite:" + databaseFile.getAbsolutePath());
    }

    private PendingInvoice.Status parseStatus(String raw) {
        try {
            return PendingInvoice.Status.valueOf(raw.toUpperCase(Locale.ROOT));
        } catch (Exception ignored) {
            return PendingInvoice.Status.PENDING;
        }
    }
}
