'use strict';

const sqlite3 = require('sqlite3').verbose();
const { open } = require('sqlite');
const path = require('path');
const fs = require('fs');

class Database {
	constructor() {
		this.db = null;
	}

	async initialize(dbPath) {
		// Ensure directory exists
		const dir = path.dirname(dbPath);
		if (!fs.existsSync(dir)) {
			fs.mkdirSync(dir, { recursive: true });
		}

		this.db = await open({
			filename: dbPath,
			driver: sqlite3.Database
		});

		// Enable foreign key enforcement (required for ON DELETE CASCADE)
		await this.db.run('PRAGMA foreign_keys = ON');

		await this.createTables();
		await this.insertDefaultPermissions();

		console.log('Database initialized:', dbPath);
		return this.db;
	}

	async createTables() {
		// Permissions table
		await this.db.exec(`
			CREATE TABLE IF NOT EXISTS permissions (
				id INTEGER PRIMARY KEY AUTOINCREMENT,
				player_name TEXT UNIQUE NOT NULL,
				role TEXT DEFAULT 'team' NOT NULL CHECK(role IN ('owner', 'team', 'readonly')),
				joined_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
			)
		`);

		// Chests table
		await this.db.exec(`
			CREATE TABLE IF NOT EXISTS chests (
				id INTEGER PRIMARY KEY AUTOINCREMENT,
				pos_x INTEGER NOT NULL,
				pos_y INTEGER NOT NULL,
				pos_z INTEGER NOT NULL,
				chest_type TEXT NOT NULL CHECK(chest_type IN ('single', 'double')),
				row INTEGER NOT NULL,
				column INTEGER NOT NULL,
				category TEXT,
				last_scan TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
				UNIQUE(pos_x, pos_y, pos_z)
			)
		`);

		// Shulkers table
		await this.db.exec(`
			CREATE TABLE IF NOT EXISTS shulkers (
				id INTEGER PRIMARY KEY AUTOINCREMENT,
				chest_id INTEGER NOT NULL,
				slot INTEGER NOT NULL,
				shulker_type TEXT DEFAULT 'shulker_box',
				category TEXT,
				item_focus TEXT,
				slot_count INTEGER DEFAULT 0,
				total_items INTEGER DEFAULT 0,
				last_scan TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
				FOREIGN KEY (chest_id) REFERENCES chests(id) ON DELETE CASCADE,
			UNIQUE(chest_id, slot)
			)
		`);

		// Shulker items table
		await this.db.exec(`
			CREATE TABLE IF NOT EXISTS shulker_items (
				id INTEGER PRIMARY KEY AUTOINCREMENT,
				shulker_id INTEGER NOT NULL,
				item_name TEXT NOT NULL,
				item_id INTEGER NOT NULL,
				slot INTEGER NOT NULL,
				count INTEGER NOT NULL,
				nbt_data TEXT,
				FOREIGN KEY (shulker_id) REFERENCES shulkers(id) ON DELETE CASCADE,
				UNIQUE(shulker_id, slot),
				CHECK(slot >= 0 AND slot <= 26),
				CHECK(count > 0 AND count <= 64)
			)
		`);

		// Trades table
		await this.db.exec(`
			CREATE TABLE IF NOT EXISTS trades (
				id INTEGER PRIMARY KEY AUTOINCREMENT,
				player_name TEXT NOT NULL,
				action TEXT NOT NULL CHECK(action IN ('deposit', 'withdraw')),
				items TEXT NOT NULL,
				timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
			)
		`);

		// Chest loose items table (non-shulker items sitting directly in chests)
		await this.db.exec(`
			CREATE TABLE IF NOT EXISTS chest_loose_items (
				id INTEGER PRIMARY KEY AUTOINCREMENT,
				chest_id INTEGER NOT NULL,
				slot INTEGER NOT NULL,
				item_name TEXT NOT NULL,
				item_id INTEGER NOT NULL,
				count INTEGER NOT NULL,
				FOREIGN KEY (chest_id) REFERENCES chests(id) ON DELETE CASCADE,
				UNIQUE(chest_id, slot)
			)
		`);

		// Item index for fast searches
		await this.db.exec(`
			CREATE TABLE IF NOT EXISTS item_index (
				id INTEGER PRIMARY KEY AUTOINCREMENT,
				item_id INTEGER UNIQUE NOT NULL,
				item_name TEXT NOT NULL,
				total_count INTEGER DEFAULT 0,
				shulker_ids TEXT,
				last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP
			)
		`);

		// Invite sites table
		await this.db.exec(`
			CREATE TABLE IF NOT EXISTS invite_sites (
				id INTEGER PRIMARY KEY AUTOINCREMENT,
				name TEXT UNIQUE NOT NULL,
				label TEXT NOT NULL,
				bot_name TEXT NOT NULL,
				description TEXT,
				created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
			)
		`);

		// Invite permissions table
		await this.db.exec(`
			CREATE TABLE IF NOT EXISTS invite_permissions (
				id INTEGER PRIMARY KEY AUTOINCREMENT,
				site_id INTEGER NOT NULL,
				player_name TEXT NOT NULL,
				FOREIGN KEY (site_id) REFERENCES invite_sites(id) ON DELETE CASCADE,
				UNIQUE(site_id, player_name)
			)
		`);
	}

	async insertDefaultPermissions() {
		const conf = require('../../conf');
		const defaultPlayers = conf.storage?.defaultPlayers || [];

		for (const player of defaultPlayers) {
			try {
				await this.db.run(
					'INSERT OR IGNORE INTO permissions (player_name, role) VALUES (?, ?)',
					[player.name, player.role]
				);
			} catch (error) {
				console.error('Error inserting default player:', player.name, error);
			}
		}
	}

	// ========================================
	// Permissions
	// ========================================

	async addPlayer(name, Role = 'team') {
		return await this.db.run(
			'INSERT INTO permissions (player_name, role) VALUES (?, ?)',
			[name, Role]
		);
	}

	async removePlayer(name) {
		return await this.db.run('DELETE FROM permissions WHERE player_name = ?', [name]);
	}

	async getPlayerRole(name) {
		const row = await this.db.get('SELECT role FROM permissions WHERE player_name = ?', [name]);
		return row ? row.role : null;
	}

	async getAllPlayers() {
		return await this.db.all('SELECT * FROM permissions ORDER BY role DESC, player_name ASC');
	}

	async checkPermission(name, requiredRole) {
		const role = await this.getPlayerRole(name);
		if (!role) return false;

		const roles = ['readonly', 'team', 'owner'];
		return roles.indexOf(role) >= roles.indexOf(requiredRole);
	}

	// ========================================
	// Chests
	// ========================================

	async upsertChest(x, y, z, chestType, row, column, category = null) {
		return await this.db.run(`
			INSERT INTO chests (pos_x, pos_y, pos_z, chest_type, row, column, category)
			VALUES (?, ?, ?, ?, ?, ?, ?)
			ON CONFLICT(pos_x, pos_y, pos_z) DO UPDATE SET
				chest_type = excluded.chest_type,
				row = excluded.row,
				column = excluded.column,
				category = excluded.category,
				last_scan = CURRENT_TIMESTAMP
		`, [x, y, z, chestType, row, column, category]);
	}

	async getChests() {
		return await this.db.all('SELECT * FROM chests ORDER BY row, column');
	}

	async getChestById(id) {
		return await this.db.get('SELECT * FROM chests WHERE id = ?', [id]);
	}

	async getChestByPosition(x, y, z) {
		return await this.db.get(
			'SELECT * FROM chests WHERE pos_x = ? AND pos_y = ? AND pos_z = ?',
			[x, y, z]
		);
	}

	async deleteOrphanChests(knownChestPositions) {
		// knownChestPositions is array of {x, y, z}
		if (knownChestPositions.length === 0) return;

		const placeholders = knownChestPositions.map(() => '(?, ?, ?)').join(', ');
		const values = knownChestPositions.flatMap(p => [p.x, p.y, p.z]);

		return await this.db.run(`
			DELETE FROM chests
			WHERE (pos_x, pos_y, pos_z) NOT IN (${placeholders})
		`, values);
	}

	// ========================================
	// Shulkers
	// ========================================

	async upsertShulker(chestId, slot, shulkerType, category = null, itemFocus = null) {
		const result = await this.db.run(`
			INSERT INTO shulkers (chest_id, slot, shulker_type, category, item_focus)
			VALUES (?, ?, ?, ?, ?)
			ON CONFLICT(chest_id, slot) DO UPDATE SET
				shulker_type = excluded.shulker_type,
				category = COALESCE(excluded.category, shulkers.category),
				item_focus = COALESCE(excluded.item_focus, shulkers.item_focus),
				last_scan = CURRENT_TIMESTAMP
		`, [chestId, slot, shulkerType, category, itemFocus]);

		return result.lastID;
	}

	async upsertAndGetShulker(chestId, slot, shulkerType, category = null) {
		await this.db.run(`
			INSERT INTO shulkers (chest_id, slot, shulker_type, category)
			VALUES (?, ?, ?, ?)
			ON CONFLICT(chest_id, slot) DO UPDATE SET
				shulker_type = excluded.shulker_type,
				category = COALESCE(excluded.category, shulkers.category),
				last_scan = CURRENT_TIMESTAMP
		`, [chestId, slot, shulkerType, category]);
		return await this.db.get(
			'SELECT * FROM shulkers WHERE chest_id = ? AND slot = ?',
			[chestId, slot]
		);
	}

	async getShulkersByChest(chestId) {
		return await this.db.all('SELECT * FROM shulkers WHERE chest_id = ? ORDER BY slot', [chestId]);
	}

	async getShulkerById(id) {
		return await this.db.get('SELECT * FROM shulkers WHERE id = ?', [id]);
	}

	async getShulkerByChestSlot(chestId, slot) {
		return await this.db.get(
			'SELECT * FROM shulkers WHERE chest_id = ? AND slot = ?',
			[chestId, slot]
		);
	}

	async updateShulkerCounts(shulkerId, slotCount, totalItems) {
		return await this.db.run(`
			UPDATE shulkers
			SET slot_count = ?, total_items = ?, last_scan = CURRENT_TIMESTAMP
			WHERE id = ?
		`, [slotCount, totalItems, shulkerId]);
	}

	async updateShulkerItemFocus(shulkerId, itemFocus) {
		return await this.db.run(
			'UPDATE shulkers SET item_focus = ? WHERE id = ?',
			[itemFocus, shulkerId]
		);
	}

	async deleteShulker(id) {
		return await this.db.run('DELETE FROM shulkers WHERE id = ?', [id]);
	}

	// Find a shulker that already stores this item type and has space (<27 slots used, not in-transit)
	async findShulkerWithSpace(itemName, excludeId = null) {
		return await this.db.get(`
			SELECT s.*, c.pos_x, c.pos_y, c.pos_z, c.chest_type
			FROM shulkers s
			INNER JOIN chests c ON c.id = s.chest_id
			WHERE s.item_focus = ? AND s.slot_count >= 0 AND s.slot_count < 27
				AND (? IS NULL OR s.id != ?)
			ORDER BY s.slot_count DESC
			LIMIT 1
		`, [itemName, excludeId, excludeId]);
	}

	// Find any empty shulker (no item_focus, no items, not in-transit)
	async findEmptyShulker(excludeId = null) {
		return await this.db.get(`
			SELECT s.*, c.pos_x, c.pos_y, c.pos_z, c.chest_type
			FROM shulkers s
			INNER JOIN chests c ON c.id = s.chest_id
			WHERE s.item_focus IS NULL AND s.total_items = 0 AND s.slot_count >= 0
				AND (? IS NULL OR s.id != ?)
			ORDER BY s.id ASC
			LIMIT 1
		`, [excludeId, excludeId]);
	}

	// Find shulkers containing a specific item (for withdrawal, excludes in-transit)
	async findShulkersWithItem(itemName) {
		return await this.db.all(`
			SELECT s.*, c.pos_x, c.pos_y, c.pos_z, c.chest_type,
				SUM(si.count) as available_count
			FROM shulkers s
			INNER JOIN chests c ON c.id = s.chest_id
			INNER JOIN shulker_items si ON si.shulker_id = s.id
			WHERE si.item_name = ? AND s.slot_count >= 0
			GROUP BY s.id
			ORDER BY available_count ASC
		`, [itemName]);
	}

	// Find item types that have more than one non-full shulker (candidates for consolidation)
	async findConsolidatableItems() {
		return await this.db.all(`
			SELECT s.item_focus, COUNT(*) as shulker_count,
				SUM(s.slot_count) as total_slots_used, SUM(s.total_items) as total_items
			FROM shulkers s
			WHERE s.item_focus IS NOT NULL
				AND s.slot_count >= 0
				AND s.slot_count < 27
			GROUP BY s.item_focus
			HAVING COUNT(*) > 1
			ORDER BY total_slots_used ASC
		`);
	}

	// Get all non-full shulkers for a given item, sorted least-full first
	async getShulkersByItemFocus(itemName) {
		return await this.db.all(`
			SELECT s.*, c.pos_x, c.pos_y, c.pos_z, c.chest_type
			FROM shulkers s
			INNER JOIN chests c ON c.id = s.chest_id
			WHERE s.item_focus = ? AND s.slot_count >= 0
			ORDER BY s.slot_count ASC
		`, [itemName]);
	}

	// Find a chest slot that doesn't have a shulker (for placing newly crafted ones)
	async findEmptyChestSlot() {
		const chests = await this.db.all(`
			SELECT c.*, COUNT(s.id) as shulker_count
			FROM chests c
			LEFT JOIN shulkers s ON s.chest_id = c.id
			GROUP BY c.id
			HAVING shulker_count < CASE WHEN c.chest_type = 'double' THEN 54 ELSE 27 END
			ORDER BY c.id ASC
			LIMIT 1
		`);

		if (!chests || chests.length === 0) return null;

		const chest = chests[0];
		const shulkers = await this.getShulkersByChest(chest.id);
		const usedSlots = new Set(shulkers.map(s => s.slot));
		const maxSlots = chest.chest_type === 'double' ? 54 : 27;

		for (let i = 0; i < maxSlots; i++) {
			if (!usedSlots.has(i)) {
				return {
					chest_id: chest.id,
					pos_x: chest.pos_x,
					pos_y: chest.pos_y,
					pos_z: chest.pos_z,
					slot: i,
				};
			}
		}

		return null;
	}

	// Get total count of a specific item across all shulkers
	async getItemTotalCount(itemName) {
		const result = await this.db.get(`
			SELECT SUM(si.count) as total
			FROM shulker_items si
			WHERE si.item_name = ?
		`, [itemName]);
		return result?.total || 0;
	}

	// ========================================
	// Shulker Items
	// ========================================

	async upsertShulkerItem(shulkerId, itemId, itemName, slot, count, nbt = null) {
		return await this.db.run(`
			INSERT INTO shulker_items (shulker_id, item_id, item_name, slot, count, nbt_data)
			VALUES (?, ?, ?, ?, ?, ?)
			ON CONFLICT(shulker_id, slot) DO UPDATE SET
				item_id = excluded.item_id,
				item_name = excluded.item_name,
				count = excluded.count,
				nbt_data = excluded.nbt_data
		`, [shulkerId, itemId, itemName, slot, count, nbt ? JSON.stringify(nbt) : null]);
	}

	async batchUpsertShulkerItems(shulkerId, items) {
		if (!items.length) return;
		await this.db.run('BEGIN TRANSACTION');
		try {
			const stmt = await this.db.prepare(`
				INSERT INTO shulker_items (shulker_id, item_id, item_name, slot, count, nbt_data)
				VALUES (?, ?, ?, ?, ?, ?)
				ON CONFLICT(shulker_id, slot) DO UPDATE SET
					item_id = excluded.item_id,
					item_name = excluded.item_name,
					count = excluded.count,
					nbt_data = excluded.nbt_data
			`);
			for (const item of items) {
				await stmt.run(shulkerId, item.id, item.name, item.slot, item.count, item.nbt ? JSON.stringify(item.nbt) : null);
			}
			await stmt.finalize();
			await this.db.run('COMMIT');
		} catch (error) {
			await this.db.run('ROLLBACK');
			throw error;
		}
	}

	async getShulkerItems(shulkerId) {
		return await this.db.all('SELECT * FROM shulker_items WHERE shulker_id = ? ORDER BY slot', [shulkerId]);
	}

	async clearShulkerItems(shulkerId) {
		return await this.db.run('DELETE FROM shulker_items WHERE shulker_id = ?', [shulkerId]);
	}

	async getShulkerItemById(id) {
		return await this.db.get('SELECT * FROM shulker_items WHERE id = ?', [id]);
	}

	/**
	 * Get all "special" items — those with NBT containing displayName, lore, or customModelData.
	 * Returns items with location info (chest position, shulker slot).
	 */
	async getSpecialItems() {
		return await this.db.all(`
			SELECT si.*, s.slot as shulker_slot, s.chest_id, s.item_focus,
				c.pos_x, c.pos_y, c.pos_z
			FROM shulker_items si
			INNER JOIN shulkers s ON s.id = si.shulker_id
			INNER JOIN chests c ON c.id = s.chest_id
			WHERE si.nbt_data IS NOT NULL
				AND si.nbt_data != 'null'
				AND (
					si.nbt_data LIKE '%"displayName"%'
					OR si.nbt_data LIKE '%"lore"%'
					OR si.nbt_data LIKE '%"customModelData"%'
				)
			ORDER BY si.item_name, si.id
		`);
	}

	// ========================================
	// Trades
	// ========================================

	async logTrade(playerName, action, items) {
		return await this.db.run(
			'INSERT INTO trades (player_name, action, items) VALUES (?, ?, ?)',
			[playerName, action, JSON.stringify(items)]
		);
	}

	async getRecentTrades(limit = 50) {
		return await this.db.all(
			'SELECT * FROM trades ORDER BY timestamp DESC LIMIT ?',
			[limit]
		);
	}

	async getTradesByPlayer(playerName, limit = 50) {
		return await this.db.all(
			'SELECT * FROM trades WHERE player_name = ? ORDER BY timestamp DESC LIMIT ?',
			[playerName, limit]
		);
	}

	// ========================================
	// Item Index
	// ========================================

	async rebuildItemIndex() {
		// Clear and rebuild from shulker_items
		await this.db.run('DELETE FROM item_index');
		await this.db.run(`
			INSERT INTO item_index (item_id, item_name, total_count, shulker_ids, last_updated)
			SELECT
				si.item_id,
				si.item_name,
				SUM(si.count) as total_count,
				GROUP_CONCAT('{"id":' || si.shulker_id || ',"count":' || si.count || '}') as shulker_ids,
				CURRENT_TIMESTAMP
			FROM shulker_items si
			GROUP BY si.item_name
		`);
		const count = await this.db.get('SELECT COUNT(*) as c FROM item_index');
		console.log(`Database: Rebuilt item index with ${count?.c || 0} entries`);
	}

	async searchItems(query) {
		// Query shulker_items plus empty shulkers as a virtual item
		if (!query) {
			return await this.db.all(`
				SELECT item_name, SUM(count) as total_count FROM (
					SELECT item_name, count FROM shulker_items
					UNION ALL
					SELECT shulker_type AS item_name, 1 AS count
					FROM shulkers WHERE total_items = 0 AND item_focus IS NULL
				)
				GROUP BY item_name
				ORDER BY total_count DESC
			`);
		}
		return await this.db.all(`
			SELECT item_name, SUM(count) as total_count FROM (
				SELECT item_name, count FROM shulker_items WHERE item_name LIKE ?
				UNION ALL
				SELECT shulker_type AS item_name, 1 AS count
				FROM shulkers WHERE total_items = 0 AND item_focus IS NULL AND shulker_type LIKE ?
			)
			GROUP BY item_name
			ORDER BY total_count DESC
		`, [`%${query}%`, `%${query}%`]);
	}

	async getItemDetails(itemId) {
		const item = await this.db.get('SELECT * FROM item_index WHERE item_id = ?', [itemId]);
		if (!item) return null;

		// Parse shulker_ids and get chest details
		const shulkerIds = JSON.parse(`[${item.shulker_ids}]`);

		const locations = await this.db.all(`
			SELECT
				s.id as shulker_id,
				s.chest_id,
				c.pos_x, c.pos_y, c.pos_z,
				js.value as count
			FROM json_each(?) as js
			INNER JOIN shulkers s ON s.id = JSON_EXTRACT(js.value, '$.id')
			INNER JOIN chests c ON c.id = s.chest_id
		`, [item.shulker_ids]);

		return { ...item, locations };
	}

	// ========================================
	// Map / Aggregation
	// ========================================

	// Get all chests with a summary of their shulker contents (for map view)
	async getChestsWithSummary() {
		return await this.db.all(`
			SELECT
				c.id, c.pos_x, c.pos_y, c.pos_z, c.chest_type, c.row, c.column, c.category,
				COUNT(s.id) as shulker_count,
				COALESCE(SUM(s.total_items), 0) as total_items,
				GROUP_CONCAT(DISTINCT s.item_focus) as item_focuses,
				COALESCE((SELECT COUNT(*) FROM chest_loose_items cli WHERE cli.chest_id = c.id), 0) as loose_item_count
			FROM chests c
			LEFT JOIN shulkers s ON s.chest_id = c.id
			GROUP BY c.id
			ORDER BY c.pos_x, c.pos_z, c.pos_y
		`);
	}

	// Get detailed shulker info with all items
	async getShulkerWithItems(shulkerId) {
		const shulker = await this.db.get(`
			SELECT s.*, c.pos_x, c.pos_y, c.pos_z
			FROM shulkers s
			INNER JOIN chests c ON c.id = s.chest_id
			WHERE s.id = ?
		`, [shulkerId]);
		if (!shulker) return null;

		const items = await this.getShulkerItems(shulkerId);
		return { ...shulker, items };
	}

	// Get all shulkers for a chest with their items
	async getChestContents(chestId) {
		const chest = await this.getChestById(chestId);
		if (!chest) return null;

		const shulkers = await this.db.all(`
			SELECT s.*,
				GROUP_CONCAT(si.item_name || ':' || si.count) as item_summary
			FROM shulkers s
			LEFT JOIN shulker_items si ON si.shulker_id = s.id
			WHERE s.chest_id = ?
			GROUP BY s.id
			ORDER BY s.slot
		`, [chestId]);

		return { chest, shulkers };
	}

	// ========================================
	// Chest Loose Items
	// ========================================

	async clearLooseItems(chestId) {
		return await this.db.run('DELETE FROM chest_loose_items WHERE chest_id = ?', [chestId]);
	}

	async upsertLooseItem(chestId, slot, itemName, itemId, count) {
		return await this.db.run(`
			INSERT INTO chest_loose_items (chest_id, slot, item_name, item_id, count)
			VALUES (?, ?, ?, ?, ?)
			ON CONFLICT(chest_id, slot) DO UPDATE SET
				item_name = excluded.item_name,
				item_id = excluded.item_id,
				count = excluded.count
		`, [chestId, slot, itemName, itemId, count]);
	}

	async batchUpsertLooseItems(chestId, items) {
		if (!items.length) return;
		await this.db.run('BEGIN TRANSACTION');
		try {
			const stmt = await this.db.prepare(`
				INSERT INTO chest_loose_items (chest_id, slot, item_name, item_id, count)
				VALUES (?, ?, ?, ?, ?)
				ON CONFLICT(chest_id, slot) DO UPDATE SET
					item_name = excluded.item_name,
					item_id = excluded.item_id,
					count = excluded.count
			`);
			for (const item of items) {
				await stmt.run(chestId, item.slot, item.name, item.id, item.count);
			}
			await stmt.finalize();
			await this.db.run('COMMIT');
		} catch (error) {
			await this.db.run('ROLLBACK');
			throw error;
		}
	}

	async getChestsWithLooseItems() {
		return await this.db.all(`
			SELECT DISTINCT c.*
			FROM chests c
			INNER JOIN chest_loose_items cli ON cli.chest_id = c.id
			ORDER BY c.id
		`);
	}

	async getAllLooseItems() {
		return await this.db.all(`
			SELECT cli.*, c.pos_x, c.pos_y, c.pos_z, c.chest_type
			FROM chest_loose_items cli
			INNER JOIN chests c ON c.id = cli.chest_id
			ORDER BY c.id, cli.slot
		`);
	}

	async deleteLooseItem(chestId, slot) {
		return await this.db.run(
			'DELETE FROM chest_loose_items WHERE chest_id = ? AND slot = ?',
			[chestId, slot]
		);
	}

	// ========================================
	// Stats
	// ========================================

	async getStats() {
		const totalItems = await this.db.get('SELECT SUM(total_items) as total FROM shulkers');
		const totalShulkers = await this.db.get('SELECT COUNT(*) as total FROM shulkers');
		const totalChests = await this.db.get('SELECT COUNT(*) as total FROM chests');
		const emptyShulkers = await this.db.get("SELECT COUNT(*) as total FROM shulkers WHERE slot_count = 0");
		const recentTrades = await this.db.get('SELECT COUNT(*) as total FROM trades WHERE timestamp > datetime("now", "-1 day")');
		const looseItems = await this.db.get('SELECT COALESCE(SUM(count), 0) as total FROM chest_loose_items');
		const chestCapacity = await this.db.get(`
			SELECT COALESCE(SUM(CASE WHEN chest_type = 'double' THEN 54 ELSE 27 END), 0) as total_slots
			FROM chests
		`);

		// Category breakdown
		const categories = await this.db.all(`
			SELECT category, COUNT(*) as count
			FROM shulkers
			WHERE category IS NOT NULL
			GROUP BY category
		`);

		const categoryMap = {};
		for (const cat of categories) {
			categoryMap[cat.category] = cat.count;
		}

		return {
			totalItems: totalItems?.total || 0,
			totalShulkers: totalShulkers?.total || 0,
			totalChests: totalChests?.total || 0,
			emptyShulkers: emptyShulkers?.total || 0,
			recentTrades: recentTrades?.total || 0,
			looseItemCount: looseItems?.total || 0,
			totalChestSlots: chestCapacity?.total_slots || 0,
			categories: categoryMap
		};
	}

	// ========================================
	// Invite Sites
	// ========================================

	async getInviteSites() {
		return await this.db.all(`
			SELECT s.*,
				GROUP_CONCAT(p.player_name) as players
			FROM invite_sites s
			LEFT JOIN invite_permissions p ON p.site_id = s.id
			GROUP BY s.id
			ORDER BY s.name
		`);
	}

	async getInviteSiteByName(name) {
		return await this.db.get('SELECT * FROM invite_sites WHERE name = ?', [name]);
	}

	async getInviteSitePlayers(siteId) {
		const rows = await this.db.all(
			'SELECT player_name FROM invite_permissions WHERE site_id = ? ORDER BY player_name',
			[siteId]
		);
		return rows.map(r => r.player_name);
	}

	async isPlayerAllowedAtSite(siteName, playerName) {
		const row = await this.db.get(`
			SELECT 1 FROM invite_sites s
			INNER JOIN invite_permissions p ON p.site_id = s.id
			WHERE s.name = ? AND p.player_name = ?
		`, [siteName, playerName]);
		return !!row;
	}

	async addInviteSite(name, label, botName, description) {
		return await this.db.run(
			'INSERT INTO invite_sites (name, label, bot_name, description) VALUES (?, ?, ?, ?)',
			[name, label, botName, description || null]
		);
	}

	async updateInviteSite(id, fields) {
		const allowed = ['name', 'label', 'bot_name', 'description'];
		const sets = [];
		const values = [];
		for (const key of allowed) {
			if (fields[key] !== undefined) {
				sets.push(`${key} = ?`);
				values.push(fields[key]);
			}
		}
		if (sets.length === 0) return;
		values.push(id);
		return await this.db.run(
			`UPDATE invite_sites SET ${sets.join(', ')} WHERE id = ?`,
			values
		);
	}

	async deleteInviteSite(id) {
		return await this.db.run('DELETE FROM invite_sites WHERE id = ?', [id]);
	}

	async addInvitePermission(siteId, playerName) {
		return await this.db.run(
			'INSERT OR IGNORE INTO invite_permissions (site_id, player_name) VALUES (?, ?)',
			[siteId, playerName]
		);
	}

	async removeInvitePermission(siteId, playerName) {
		return await this.db.run(
			'DELETE FROM invite_permissions WHERE site_id = ? AND player_name = ?',
			[siteId, playerName]
		);
	}

	async seedInviteSites(sites) {
		for (const site of sites) {
			try {
				await this.db.run(
					'INSERT OR IGNORE INTO invite_sites (name, label, bot_name, description) VALUES (?, ?, ?, ?)',
					[site.name, site.label, site.bot, site.description || null]
				);
				const row = await this.getInviteSiteByName(site.name);
				if (row && site.allowed) {
					for (const player of site.allowed) {
						await this.addInvitePermission(row.id, player);
					}
				}
			} catch (error) {
				console.error('Error seeding invite site:', site.name, error);
			}
		}
	}

	async close() {
		if (this.db) {
			await this.db.close();
			this.db = null;
		}
	}
}

module.exports = new Database();