--[[
    Cerebro - Database Schema
    Extracted from Database.lua (Phase 2a decomposition)

    Default database structure, config defaults, initial values,
    AceDB initialization, and the guest stub for scope-locked clients.

    @module Database.Schema
    @version 0.12.0
]]--

local AddonName = "Cerebro"
local Cerebro = LibStub("AceAddon-3.0"):GetAddon(AddonName)

-- Default database structure
local defaults = {
	global = {
		-- Version for migration tracking
		dbVersion = 1,

		-- RCLC Deduplication (No migration needed, AceDB backfills)
		-- Key: session|winner|itemID|response|status -> timestamp
		rclcAwardDedupe = {},

		-- RCLC Import Deduplication
		-- Key: rclcId (servertime|player|itemString) -> timestamp
		rclcImportedIds = {},
		-- Last RCLC import metadata (for /cb rclc status)
		rclcLastImport = nil,  -- { timestamp, sinceDate, imported, voided, skipped, importedBy }

		-- Sync status tracking (for /cb status)
		-- lastOfficerSync: Unix timestamp of last SYNC_DATA receipt from officer
		syncStatus = {
			lastOfficerSync = 0,
		},

		-- Factory Reset Quarantine System
		-- Epoch: Increments on every factory reset; used to reject stale sync payloads
		-- Quarantine: Blocks sync for 15 min after reset to prevent data restoration
		resetEpoch = 0,
		resetQuarantine = nil,  -- { at = timestamp }

		-- RCLC Integration (v1.0.0+)
		-- Stores per-button GP multipliers/overrides
		rclc = {
			groupButtons = {}, -- [groupName] = { [buttonIndex] = "100%" or "50" }
			responses = {},    -- Legacy/Fallback if needed
		},

		-- Guild Scope (Guild Locking)
		-- Binds database to a specific guild to prevent alt data leakage
		-- See SPEC.md §3.5 and SECURITY_MODEL.md §7
		scope = nil,  -- { guildGUID, guildName, realm, setBy, setAt, migrated }

		-- Purge Ledger (Negative Ledger pattern)
		-- Tracks permanently deleted players to prevent sync resurrection
		-- See docs/proposals/PLAYER_PURGE.md
		purgedPlayers = {},  -- { ["Name-Realm"] = timestamp }

		-- Tracks blocked entries (corrupted names, fake entries) — prevents sync resurrection
		-- Synced to ALL clients via V2 snapshot. Use /cb block to add, /cb unblock to remove.
		blockedPlayers = {},  -- { ["Name-Realm"] = timestamp }

		-- Season tracking (for M+ week calculation)
		-- Stores last seen season ID to detect season changes
		lastKnownSeasonID = 0,

		-- Attendance Tracking (per-season)
		-- Reset when season changes or factory reset
		seasonBossKills = 0,      -- Total boss kills this season
		seasonID = 0,             -- Current M+ season ID for reset detection

		-- Minimap icon position (for LibDBIcon)
		minimap = {
			hide = false,
		},

		-- Configuration
		config = {
			debug = false,
			debugLevel = "off",  -- "off" | "simple" | "technical" | "both"

			epgp = {
				baseGP = 200,
				minEP = 500,
				decayRate = 0.10, -- 10%
				epOnTime = 700,              -- Raid Start EP
				epFarmBoss = 15,             -- Farm Boss EP
				epProgBoss = 50,             -- Prog Boss EP
				epFullAttendance = 700,      -- Raid End EP
				epProgressionHourly = 15,    -- Progression Interval EP
				progressionIntervalMin = 30, -- Minutes between progression EP ticks
				benchMultiplier = 1.01,      -- 101% for standby
				offspecMultiplier = 0.5,
				minorUpgradeMultiplier = 0.5,
				minGP = 100, -- Minimum GP
				decay = 15,  -- 15% Weekly Decay
				allowTestAwards = false, -- Allow RCLC Test Mode to charge GP
				onTimeGracePeriod = 0,   -- Minutes after session start for on-time arrivals

				-- Slot Weights (Base GP)
				slotWeights = {
					["INVTYPE_HEAD"] = 1500,
					["INVTYPE_NECK"] = 840,
					["INVTYPE_SHOULDER"] = 1125,
					["INVTYPE_CLOAK"] = 500,       -- Back
					["INVTYPE_CHEST"] = 1500,
					["INVTYPE_ROBE"] = 1500,       -- Chest (Robe variant)
					["INVTYPE_WRIST"] = 840,
					["INVTYPE_HAND"] = 1125,
					["INVTYPE_WAIST"] = 1125,
					["INVTYPE_LEGS"] = 1500,
					["INVTYPE_FEET"] = 1125,
					["INVTYPE_FINGER"] = 840,      -- Ring
					["INVTYPE_TRINKET"] = 1875,

					["INVTYPE_WEAPON"] = 1025,     -- Weapon (1H)
					["INVTYPE_SHIELD"] = 1025,
					["INVTYPE_RANGED"] = 2050,     -- Classic ranged slot
					["INVTYPE_RANGEDRIGHT"] = 2050, -- Retail ranged (bows, guns, crossbows, wands)
					["INVTYPE_2HWEAPON"] = 2050,   -- Weapon (2H)
					["INVTYPE_WEAPONMAINHAND"] = 1025, -- Main Hand
					["INVTYPE_WEAPONOFFHAND"] = 1025,  -- Off Hand
					["INVTYPE_HOLDABLE"] = 1025,   -- Off Hand (Frill)

					-- Special "Curio" slot for Context Tokens
					["Curio"] = 1350,
					["INVTYPE_RECIPE"] = 0,

					-- Collectible / Non-Gear Slots (default 0 — officers enable as needed)
					["INVTYPE_TOY"] = 0,         -- Toys (collectible fun items)
					["INVTYPE_PET"] = 0,         -- Companion / Battle Pets
					["INVTYPE_MOUNT"] = 0,       -- Mounts
					["INVTYPE_COSMETIC"] = 0,    -- Cosmetics (appearance-only gear)
					["INVTYPE_DECOR"] = 0,       -- Decor / Delves decoration items
				},

				-- Duplicate Slot Multipliers
				-- When awarding an item for a slot that some players only need 1 of,
				-- a popup lets the officer charge reduced GP (e.g. 2H for non-DW classes).
				duplicateSlotMultipliers = {
					["INVTYPE_2HWEAPON"] = 0.5,   -- 50% GP for single 2H slot classes
				},

				-- Difficulty Multipliers
				-- Keys are STRINGS ("14", "15") to match AceGUI Select control requirements
				-- 14=Normal, 15=Heroic, 16=Mythic (Standard WoW Difficulty IDs)
				difficultyMultipliers = {
					["14"] = 1.00, -- Normal
					["15"] = 1.15, -- Heroic
					["16"] = 1.30, -- Mythic
				},

				-- Mythic+ Configuration
				mplusBase = 100,             -- Base EP for completing any M+ dungeon
				mplusPerLevel = 5,           -- EP per keystone level
				mplusMinLevel = 2,           -- Min keystone level for scaling (default 2)
				mplusMaxLevel = 12,          -- Cap level for EP calculation
				-- Guild Bonuses (Percentage "50%" or Flat "50")
				-- Keys: mplusGuildBonus2..5 (2 members to 5 members)
				mplusGuildBonus2 = "0%",
				mplusGuildBonus3 = "0%",
				mplusGuildBonus4 = "0%",
				mplusGuildBonus5 = "0%",
				readinessCap = 400,          -- Weekly pre-raid EP cap

				-- Season Start Override (officer-set, Unix timestamp)
				-- Used when hardcoded season start dates are missing/incorrect
				seasonStartOverride = nil,

				-- Calendar Sign-Up EP
				epSignUp = 0,                -- EP for calendar sign-up (0 = disabled by default)
				signupStatuses = {           -- Qualifying RSVP statuses
					[1] = true,              -- Accepted
					[3] = true,              -- Confirmed
				},
			},

			-- Per-section LWW timestamps for config sync
			-- Updated automatically when an officer changes any setting in that section
			configLastUpdate = {
				epgp = 0,
				prep = 0,
				sync = 0,
				admin = 0,
			},

			prep = {
				allOrNothing = false,             -- Master toggle: pass everything or get zero EP
				requireGems = true,
				requireEnchants = true,
				requireFood = true,               -- Require Well Fed food buff
				requireFlask = true,              -- Require Flask / Elixir buff
				requireAugmentRune = true,        -- Require Augment Rune buff
				requireVantusRune = false,        -- Require Vantus Rune buff
				requireWeaponEnhancement = true,  -- Require weapon oil/stone
				requireMinIlvl = false,           -- Require minimum average item level
				minIlvl = 237,                    -- Minimum item level for prep checks
				-- Per-category EP awards
				minIlvlEP = 0,                    -- EP for meeting min iLvl (flat)
				gemEPPerSocket = 1,               -- EP per valid gem (proportional)
				enchantEPPerSlot = 1,             -- EP per valid enchant (proportional)
				foodEP = 1,                       -- EP for having food buff (flat)
				flaskEP = 1,                      -- EP for having flask (flat)
				augmentRuneEP = 1,                -- EP for augment rune (flat)
				vantusRuneEP = 1,                 -- EP for vantus rune (flat)
				weaponEnhancementEP = 1,          -- EP for weapon buff (flat)
			},

			ui = {
				showMinimapIcon = true,
				lockFrames = false,
			},

			-- Admin/Governance Settings
			admin = {
				allowOfficerFactoryReset = false, -- GM-only by default
				allowOfficerScopeReset = false,   -- Officers can switch guilds (GM-only by default)
			},

			-- Sync Configuration (Fix 5 + Fix 2 merged)
			-- Synced to all clients via config payload in BuildSyncPayload
			sync = {
				-- Guild ranks visible in Standings and included in sync payload
				-- Key: rankIndex (0=GM, 1=Officer, etc.), Value: true/false
				-- Players with non-visible ranks are excluded from sync and ANN_SELF
				visibleRanks = {},  -- No defaults; officers configure on first use
				-- Guild ranks eligible for Sync Leader election (Fix 2)
				-- Only candidates with these ranks participate in leader election
				syncLeaderRanks = { [0] = true, [1] = true },
			},
		},

		-- Factory Reset Backup (Rolling single backup)
		-- Created before any factory reset; can be restored by GM only
		-- Includes resetEpoch so restore doesn't reject future syncs as stale
		backup = nil, -- { timestamp, version, players, transactions, audit, epgpConfig, resetEpoch }

		-- Player data (EP, GP, PR)
		-- Delta Sync v2 adds: rev (global revision), fieldRev (per-field revisions)
		players = {
			-- ["PlayerName-RealmName"] = {
			--     ep = 0,
			--     gp = 200,
			--     lifetimeEarned = 0,
			--     lifetimeSpent = 0,
			--     class = "WARRIOR",
			--     lastSeen = 0,
			--     attendance = { total = 0, onTime = 0, prepared = 0 },
			--     rev = 0,           -- Delta Sync v2: player revision
			--     fieldRev = {},     -- Delta Sync v2: { ep = N, gp = N, status = N, ... }
			-- }
		},

		-- Wishlists
		wishlists = {
			-- ["PlayerName-RealmName"] = {
			--     [itemID] = {
			--         priority = 1,
			--         autoBid = 50,
			--         notes = "",
			--         willPass = false,
			--         encounterID = 12345,    -- Which boss drops this
			--         bossRank = 1,           -- Rank among items from same boss (1=highest)
			--         addedTime = 0,
			--     }
			-- }
		},

		-- Current raid session
		currentSession = nil,

		-- Historical sessions
		history = {
			-- ["sessionID"] = { session data }
		},

		-- EP/GP transaction log
		transactions = {
			-- {
			--     id = "tx_001",
			--     timestamp = 0,
			--     player = "PlayerName-RealmName",
			--     amount = 0,
			--     balance = 0,
			--     reason = "BOSS_KILL",
			--     details = {},
			--     sessionID = nil,
			-- }
		},

		-- Item awards
		awards = {
			-- {
			--     timestamp = 0,
			--     itemID = 0,
			--     itemLink = "",
			--     winner = "PlayerName-RealmName",
			--     amount = 0,
			--     source = "Boss Name",
			--     sessionID = nil,
			--     allBids = {},
			--     passedTo = nil,
			-- }
		},
		-- Total boss kills tracked
		totalBossKills = 0,

		-- Officer Audit Log (Config changes, Admin actions)
		audit = {
			-- {
			--     timestamp = 0,
			--     admin = "Name",
			--     action = "GP_UPDATE",
			--     details = "Updated Head to 1000",
			-- }
		},

		-- Pending Discord Webhook payloads
		discordQueue = {
			-- [timestamp] = { payload = "json_string", sent = false }
		},

		-- Last decay timestamp
		lastDecay = 0,

		-- Local M+ run history (per-character)
		mplusRuns = {},

		-- Guild-wide M+ run history (officer-only, synced between officers)
		-- 500 cap, FIFO - stores runs pushed from all raiders
		guildMplusRuns = {},

		-- Delta Sync Protocol v2 State (Officer)
		-- See docs/DELTA_SYNC_IMPLEMENTATION.md for protocol details
		syncState = {
			nextRev = 0,          -- Monotonic revision generator for player fields
			nextSeq = 0,          -- Monotonic sequence for outgoing SYNC_DATA
			lastFullSyncAt = 0,   -- Timestamp of last full snapshot
			dirtyPlayers = {},    -- { [name] = { [field] = true } } dirty tracking
			dirtyPurgesAt = 0,    -- Last purge timestamp included in sync
			dirtyMplusAt = 0,     -- Last M+ receivedAt included in sync
			pendingSyncs = {},    -- { [seq] = { sentAt, target, retryCount } } for WHISPER only
			maxPendingSyncs = 5,  -- Cap to prevent unbounded memory growth
			-- Raid Sync v2: Peer Status Tracking (Officer-only)
			-- Tracks all raid members' sync freshness via SYNC_ACK responses
			lastBroadcastSeq = 0, -- seq of last RAID broadcast SYNC_DATA
			lastBroadcastAt = 0,  -- timestamp of last RAID broadcast
			peerStatus = {},      -- { [fullName] = { lastSeenAt, lastAckSeq, lastAckAt, ... } }
			-- Raid Sync v2: Configurable intervals (officer-tuneable)
			raidSyncConfig = {
				fullSyncInterval = 600,     -- 10 min safety net for forced full sync
				heartbeatInterval = 30,     -- seconds between OnHeartbeat ticks
				targetedSyncThreshold = 5,  -- stale peer count: <= this uses whisper, > uses broadcast
				gossipDigestInterval = 180, -- 3 min: officer-to-officer anti-entropy (v0.9.3+)
			},
		},

		-- Delta Sync Protocol v2 State (Raider)
		lastSyncSeq = 0,          -- Last SYNC_DATA.seq applied
		receiptAckedBy = {},      -- { [officerName] = { ts, seq } } cursor per officer
		staleCursors = {},        -- { [officerName] = true } marks failed receipt syncs

		-- Receipt Cursor Tracking (see docs/proposals/Receipt_cursor_tracking.md)
		receiptSeq = 0,           -- Monotonic sequence for locally created receipts
		receiptSyncState = {
			pendingBatches = {},  -- { [batchId] = { sentAt, target, cursor, retryCount } }
			maxBatchSize = 40,    -- Receipts per batch
			lastResetAt = 0,      -- Timestamp of last cursor state reset
		},
	},

	-- Per-character settings
	profile = {
		ui = {
			mainFrame = {
				point = "CENTER",
				x = 0,
				y = 0,
				width = 700,
				height = 500,
			},
		},

		notifications = {
			showBidConfirm = true,
			showEPGPChanges = true,
			showItemWon = true,
			playSound = true,
		},
	},
}

--[[=============================================
	Guild Scope - Guest Stub

	Frozen read-only player stub returned when scope
	verification fails. Prevents Lua errors at call sites
	while blocking data writes.
===============================================]]

-- Proxy pattern: real data is hidden, proxy blocks ALL writes
local GUEST_STUB_DATA = {
	ep = 0,
	gp = 0,
	pr = 0,
	status = "Guest",
	class = nil,
	lastSeen = 0,
	attendance = { total = 0, onTime = 0, prepared = 0 },
	bossKillsPresent = 0,
	role = nil,
}
local GUEST_STUB = setmetatable({}, {
	__index = GUEST_STUB_DATA,        -- Reads proxy to real data
	__newindex = function() end,      -- ALL writes silently ignored
	__metatable = false,              -- Prevent tampering
})

-- Expose for testing
Cerebro.GUEST_STUB = GUEST_STUB

--[[=============================================
	Database Initialization
===============================================]]


function Cerebro:InitDatabase()
	-- Initialize AceDB
	self.db = LibStub("AceDB-3.0"):New("CerebroDB", defaults, true)

	-- Run migrations if needed
	self:MigrateDatabase()

	-- Cleanup stale purge ledger entries (30-day TTL)
	local ok1, err1 = pcall(function() self:CleanupPurgeLedger() end)
	if not ok1 then self:Print("|cffff0000[INIT ERROR] CleanupPurgeLedger: " .. tostring(err1) .. "|r") end

	-- Force signupStatuses out of AceDB's metatable proxy into raw SavedVariables.
	-- AceDB serves default subtables via proxy; writes to them don't persist unless
	-- explicitly materialized. Without this, config sync overwrites them with defaults.
	local ok2, err2 = pcall(function()
		local epgpConfig = self.db.global.config and self.db.global.config.epgp
		if epgpConfig and not rawget(epgpConfig, "signupStatuses") then
			local statusDefaults = { [1] = true, [2] = false, [3] = true, [4] = false, [5] = false, [8] = false }
			local existing = epgpConfig.signupStatuses
			if existing then
				for k, v in pairs(existing) do
					statusDefaults[k] = v
				end
			end
			epgpConfig.signupStatuses = statusDefaults
		end
	end)
	if not ok2 then self:Print("|cffff0000[INIT ERROR] signupStatuses: " .. tostring(err2) .. "|r") end

	-- Register callbacks
	self.db.RegisterCallback(self, "OnProfileChanged", "RefreshConfig")
	self.db.RegisterCallback(self, "OnProfileCopied", "RefreshConfig")
	self.db.RegisterCallback(self, "OnProfileReset", "RefreshConfig")

	self:Debug("Database initialized (version " .. self.db.global.dbVersion .. ")")
end

function Cerebro:RefreshConfig()
	-- Reload UI or settings when profile changes
	self:Print("Settings reloaded.")
end
