--[[
	Cerebro - RCLootCouncil Bridge
	
	PURPOSE:
	Integrates EPGP data into RCLootCouncil's Voting Frame and handles automatic
	GP charging when items are awarded via RCLC.
	
	RESPONSIBILITIES:
	1. Inject "PR" column into RCLC Voting Frame (Officer-only view)
	2. Listen for 'RCMLAwardSuccess' messages from RCLC
	3. Charge GP for awarded items (Officer-only action)
	4. Prevent duplicate charges (Idempotency) via deterministic keys
	
	SECURITY CONTRACTS:
	
	# RBAC Enforcement
	  - Officer Validation: Checks `Cerebro:IsOfficer(Cerebro:GetFullPlayerName(sender))`
	  - No Trust: Ignores non-officer payloads completely
	  - Failure Mode: Silent ignore + debug log
	
	# Idempotency (Critical)
	  - Duplicate Awards: RCLC may fire callbacks multiple times (e.g. reload, re-announce)
	  - Dedupe Key: `sessionID|winnerFull|itemID|response|status`
	  - Persistence: Award keys stored in `db.global.rclcAwardDedupe`
	  - Atomic Commit: Key only written AFTER successful ChargeGP
	
	# Logic Rules
	  - Pass/Greed/Disenchant: Ignored (no GP charge)
	  - Test Mode: Only charges GP if `db.global.config.epgp.allowTestAwards` is strictly true
	  - Item ID: Used for dedupe key (more stable than Link string variations)
	
	EXTERNAL DEPENDENCIES:
	- RCLootCouncil (LibStub("AceAddon-3.0"):GetAddon("RCLootCouncil"))
	- Cerebro.Modules.EPGP (ChargeGP)
	- Cerebro.Database (rclcAwardDedupe)
]]--

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

-- AceHook-3.0 is provided by RCLootCouncil, not bundled with Cerebro.
-- If RCLC isn't installed, this module can't function — fail gracefully.
local ok, Bridge = pcall(function()
	return Cerebro:NewModule("RCLC_Bridge", "AceEvent-3.0", "AceComm-3.0", "AceHook-3.0")
end)
if not ok then
	return  -- AceHook-3.0 not available (RCLootCouncil not installed)
end

-- Constants
local RCLC_ADDON_NAME = "RCLootCouncil"

-- Localized strings (hardcoded to avoid AceLocale dependency in bridge)
local L = {
	["gp_value_help"] = "GP Value (e.g. 100% or 50). Percentage is relative to the item's Base GP.",
}

-- Track hooked buttons for the Option Menu (Weak table to allow GC)
local hookedButtons = setmetatable({}, { __mode = "k" })

function Bridge:OnInitialize()
	-- Wait for RCLC
	self:RegisterEvent("ADDON_LOADED", "OnAddonLoaded")
end

function Bridge:OnAddonLoaded(event, addonName)
	if addonName == "RCLootCouncil" then
		self:Enable()
		self:UnregisterEvent("ADDON_LOADED")
	end
end

function Bridge:OnEnable()
	local RCLC = LibStub("AceAddon-3.0"):GetAddon(RCLC_ADDON_NAME, true)
	if not RCLC then
		Cerebro:Debug("RCLC Bridge: RCLootCouncil not found. Bridge disabled.")
		self:Disable()
		return
	end
	
	Cerebro:Print("Bridging with RCLootCouncil...")
	
	-- 1. Hook Options Table
	-- We hook the `OptionsTable` method to inject our custom "GP" fields into the
	-- Master Looter -> Buttons tab whenever RCLC rebuilds its options.
	if not self:IsHooked(RCLC, "OptionsTable") then
		self:SecureHook(RCLC, "OptionsTable", "InjectButtonOptions")
	end
	
	-- 2. Hook UI Elements
	-- Inject "PR" column into Voting Frame (Officer View)
	self:InjectOfficerColumns()
	-- Inject "Your PR" label into Loot Frame (Candidate View)
	self:InjectCandidateLabel()
	
	-- 3. Hook Award Events
	-- We use two methods to detect awards to ensure compatibility across RCLC versions
	self:RegisterMessage("RCLootCouncil_Award_Item", "OnRCLCAward")
	self:RegisterMessage("RCMLAwardSuccess", "OnRCLCAward")
	
	-- Method B: SecureHook on AnnounceAward (Fallback/Guarantee)
	local ML = RCLC:GetModule("RCLootCouncilML", true)
	if ML then
		self:SecureHook(ML, "AnnounceAward", "OnAnnounceAward")
	end
	
	-- Attempt initial injection (in case options are already loaded)
	self:InjectButtonOptions()
end

--[[=============================================
	Logic & RBAC
===============================================]]

-- Deterministic Key Generation
function Bridge:GetAwardKey(session, winner, itemLink, response, isTest)
	local winnerFull = Cerebro:GetFullPlayerName(winner)
	local itemID = C_Item.GetItemInfoInstant(itemLink) or 0
	local status = isTest and "TEST" or "LIVE"
	
	-- For test mode, itemID is always 0, so add timestamp to make key unique
	local uniqueSuffix = ""
	if isTest and itemID == 0 then
		uniqueSuffix = "|" .. time()
	end
	
	-- Key: session|winner|itemID|response|status[|timestamp for test]
	return string.format("%s|%s|%d|%s|%s%s",
		session or "unknown",
		winnerFull or "unknown",
		itemID,
		response or "unknown",
		status,
		uniqueSuffix
	)
end

-- Hook Handler for `AnnounceAward`
-- This function is called by our SecureHook when RCLC announces an award.
-- We map the arguments to our unified `OnRCLCAward` handler.
-- NOTE: `mlObj` is the first argument because SecureHook passes the object `self`.
function Bridge:OnAnnounceAward(mlObj, name, link, response, roll, session, changeAward, owner)
	-- Map arguments to our internal logic
	self:OnRCLCAward("HOOKed", session, name, link, nil, response, nil)
end

function Bridge:OnRCLCAward(msgName, session, winner, itemLink, reason, response, responseType, ...)
	-- RCLC Args: session(number), winner(name), itemLink, reason(text), response(text), type(responseID), ...
	-- Note: Args might vary by RCLC version, we check availability carefully.
	
	-- Log entry point (debug only)
	Cerebro:Debug("RCLC Bridge: Received award event")
	Cerebro:Debug(string.format("  Winner: %s, Item: %s, Response: %s", tostring(winner), tostring(itemLink), tostring(response)))

	-- 1. RBAC: Only officers execute this logic
	if not Cerebro:IsOfficer() then
		Cerebro:Debug("RCLC Bridge: Ignoring award (not an officer)")
		return
	end
	
	-- 2. Validate Inputs
	if not winner or not itemLink then
		Cerebro:Debug("RCLC Bridge: Missing winner or itemLink")
		return
	end
	
	-- 3. Try to get real item link from RCLC's lootTable (works even in test mode)
	local realItemLink = itemLink
	local RCLootCouncil = LibStub("AceAddon-3.0"):GetAddon("RCLootCouncil", true)
	if RCLootCouncil then
		local RCLootCouncilML = RCLootCouncil:GetModule("RCLootCouncilML", true)
		if RCLootCouncilML and RCLootCouncilML.lootTable and RCLootCouncilML.lootTable[session] then
			local lootEntry = RCLootCouncilML.lootTable[session]
			if lootEntry.link and lootEntry.link ~= "test_mode" then
				realItemLink = lootEntry.link
				Cerebro:Debug("  Found real item link from lootTable: " .. tostring(realItemLink))
			end
		end
	end
	
	-- Update itemLink if we found the real one
	itemLink = realItemLink
	
	-- Normalize Winner
	local winnerFull = Cerebro:GetFullPlayerName(winner)
	
	-- 3. Determine Response (Prefer ID/Type if available)
	-- RCLC usually passes: response (text), type (responseID/tier)
	-- We use type (responseID) if possible, fallback to text
	local responseText = response
	
	-- 4. Check Pass/Greed/Disenchant (No GP Charge)
	-- Standard RCLC IDs: 1=Mainspec, 2=Minor, 3=Offspec, 4=Greed, 5=Pass (varies by config)
	-- Note: "Autopass" should NOT be filtered - it's a valid loot award
	local lowerResp = string.lower(responseText or "")
	local isPassResponse = (lowerResp == "pass") or 
	                       (string.find(lowerResp, "^pass$")) or
	                       (string.find(lowerResp, "greed")) or 
	                       (string.find(lowerResp, "disenchant"))
	
	-- Exclude autopass from the filter (autopass IS a valid loot award)
	if lowerResp:find("autopass") then
		isPassResponse = false
	end
	
	if isPassResponse then
		Cerebro:Debug("RCLC Bridge: Ignoring award (Reason: " .. lowerResp .. ")")
		return
	end
	
	-- 5. Test Mode Check
	local RCLootCouncilAddon = LibStub("AceAddon-3.0"):GetAddon("RCLootCouncil", true)
	local isTest = false
	if RCLootCouncilAddon and RCLootCouncilAddon.testMode then isTest = true end
	
	-- Also detect test mode from itemLink == "test_mode"
	if itemLink == "test_mode" then
		isTest = true
		Cerebro:Debug("  Detected test mode from itemLink")
	end
	
	local config = Cerebro.db.global.config.epgp
	if isTest and not config.allowTestAwards then
		Cerebro:Debug("RCLC Bridge: Ignoring Test Mode award (allowTestAwards=false)")
		return
	end

	-- 6. Idempotency Check
	local awardKey = self:GetAwardKey(session, winner, itemLink, responseText, isTest)
	if Cerebro.db.global.rclcAwardDedupe[awardKey] then
		Cerebro:Debug("RCLC Bridge: Ignoring duplicate award (Key: " .. awardKey .. ")")
		return
	end
	
	-- 7. Calculate GP (Get cost from EPGP module)
	-- Use current instance difficulty so Heroic/Mythic multipliers apply
	local _, _, difficultyID = GetInstanceInfo()
	local gp = Cerebro.modules.EPGP:GetItemGP(itemLink, difficultyID)
	
	-- Handle test mode: RCLC sends "test_mode" as itemLink, which returns 0 GP
	-- Use a default test GP value (Trinket/Weapon tier = 800)
	if isTest and (not gp or gp == 0) then
		gp = config.slotWeights["INVTYPE_TRINKET"] or 800
		Cerebro:Debug(string.format("  Using test mode default GP: %d", gp))
	end
	
	-- 7.5 Apply Button Modifier
	-- We look up the button's configured GP scalar (percentage or flat value).
	local modifier = self:GetRCLCResponseGP(responseText)
	
	if modifier then
		if type(modifier) == "string" and modifier:find("%%") then
			local pct = tonumber(modifier:match("(%d+)%%"))
			if pct then
				gp = math.floor(gp * (pct / 100))
				Cerebro:Debug(string.format("RCLC Bridge: Applied %% modifier: %d%% -> New GP: %d", pct, gp))
			end
		elseif tonumber(modifier) then
			gp = tonumber(modifier)
			Cerebro:Debug(string.format("RCLC Bridge: Applied Flat modifier -> New GP: %d", gp))
		end
	end
	
	if not gp or gp == 0 then
		Cerebro:Debug("RCLC Bridge: Item has 0 GP, skipping charge.")
		return
	end
	
	-- 8. Build rich details for History
	local displayItem = itemLink
	local displaySlot
	local equipLoc
	
	if itemLink ~= "test_mode" then
		local _, _, _, eqLoc = C_Item.GetItemInfoInstant(itemLink)
		equipLoc = eqLoc
		displaySlot = equipLoc and Cerebro:GetFriendlySlotName(equipLoc) or "Unknown"
	else
		-- Test mode: RCLC doesn't provide real item data
		displayItem = "[Test Item]"
		displaySlot = "Test Mode"
	end
	
	local awardDetails = {
		item = displayItem,
		slot = displaySlot,
		response = responseText,
		isTest = isTest
	}
	
	-- Helper: finalize the GP charge (used by both normal path and popup callbacks)
	local function finalizeCharge(finalGP)
		-- 9. Charge GP (Atomic Commit)
		local success = Cerebro.modules.EPGP:ChargeGP(winnerFull, finalGP, "LOOT", awardDetails)
		
		if success then
			-- 10. Persist Dedupe Key
			Cerebro.db.global.rclcAwardDedupe[awardKey] = time()
			
			-- 11. Announce
			Cerebro:Print(string.format("RCLC Integration: Charged %d GP to %s for %s", 
				finalGP, Cerebro:ColorPlayerName(winnerFull), displayItem))
		else
			Cerebro:Debug("RCLC Bridge: Failed to charge GP (EPGP module rejected)")
		end
	end
	
	-- 8.5 Duplicate Slot Check: If this slot has a configured multiplier,
	-- show a popup asking the officer to charge full or reduced GP.
	-- Only applies to Warriors (Titan's Grip allows dual-wielding 2H weapons).
	local dupMultipliers = config.duplicateSlotMultipliers
	local dupMult = dupMultipliers and equipLoc and dupMultipliers[equipLoc]
	
	-- Check winner's class — only Warriors can dual-wield 2H weapons
	local winnerData = Cerebro:GetPlayerData(winnerFull)
	local winnerClass = winnerData and winnerData.class
	if winnerClass ~= "WARRIOR" then dupMult = nil end
	
	if dupMult and dupMult > 0 and dupMult < 1 then
		local reducedGP = math.floor(gp * dupMult)
		local _pctLabel = math.floor(dupMult * 100)
		
		-- Set dedupe key NOW to prevent double-charge from RCLC's dual event paths
		-- (award fires through both callback and hook before popup is clicked)
		Cerebro.db.global.rclcAwardDedupe[awardKey] = time()
		
		StaticPopupDialogs["CEREBRO_DUPLICATE_SLOT_GP"] = {
			text = "Awarding " .. displayItem .. " to " .. Cerebro:GetShortPlayerName(winnerFull) ..
				".\n\nCharge full GP (" .. Cerebro:FormatNumber(gp) ..
				") or reduced GP (" .. Cerebro:FormatNumber(reducedGP) .. ")?",
			button1 = string.format("Full (%s GP)", Cerebro:FormatNumber(gp)),
			button2 = string.format("Reduced (%s GP)", Cerebro:FormatNumber(reducedGP)),
			OnAccept = function()
				finalizeCharge(gp)
			end,
			OnCancel = function()
				finalizeCharge(reducedGP)
			end,
			timeout = 0,
			whileDead = true,
			hideOnEscape = false,
			preferredIndex = 4,
		}
		StaticPopup_Show("CEREBRO_DUPLICATE_SLOT_GP")
	else
		-- Normal path: no duplicate multiplier, charge immediately
		finalizeCharge(gp)
	end
end

--[[=============================================
	Options Injection
===============================================]]

-- Injects GP configuration fields into RCLC's AceConfig options table.
-- Called via SecureHook whenever RCLC rebuilds its options, and once on enable.
function Bridge:InjectButtonOptions()
	local RCLC = LibStub("AceAddon-3.0"):GetAddon(RCLC_ADDON_NAME, true)
	if not RCLC then return end
	
	-- RCLC stores its options table in `addon.options` after generation
	local options = RCLC.options
	if not options then return end
	
	-- Ensure DB structure exists
	if not Cerebro.db or not Cerebro.db.global then return end
	Cerebro.db.global.rclc = Cerebro.db.global.rclc or {}
	Cerebro.db.global.rclc.groupButtons = Cerebro.db.global.rclc.groupButtons or {}
	
	-- Retrieve active button groups from RCLC profile
	local buttonGroups = CopyTable(RCLC.db.profile.enabledButtons or {})
	buttonGroups.buttonOptions = true -- Include default category
	
	-- Navigate to Buttons Tab in the options table
	local buttonsTab = options.args
		and options.args.mlSettings
		and options.args.mlSettings.args
		and options.args.mlSettings.args.buttonsTab
	
	if not buttonsTab or not buttonsTab.args then return end
	
	local injectedCount = 0
	
	for groupName in pairs(buttonGroups) do
		local dbKey = (groupName == "buttonOptions") and "default" or groupName
		
		-- Ensure storage for this group
		if type(Cerebro.db.global.rclc.groupButtons[dbKey]) ~= "table" then
			Cerebro.db.global.rclc.groupButtons[dbKey] = {}
		end
		
		local groupNode = buttonsTab.args[groupName] and buttonsTab.args[groupName].args
		
		if groupNode then
			local numButtons = RCLC.db.profile.buttons[dbKey] and RCLC.db.profile.buttons[dbKey].numButtons or 0
			
			for i = 1, numButtons do
				local idx = i
				local storeKey = dbKey
				local key = "cerebro_gp_" .. idx
				
				-- Inject GP Input Field next to each button config
				if not groupNode[key] then
					groupNode[key] = {
						order = idx * 5 + 3.1, -- Place near the button settings
						name = "GP",
						desc = L["gp_value_help"],
						type = "input",
						width = 0.5,
						pattern = "^%d+%%?$", -- Accepts numbers or percentages (e.g. "100%")
						get = function()
							local gt = Cerebro.db.global.rclc.groupButtons[storeKey]
							return gt and gt[idx] or "100%"
						end,
						set = function(info, value)
							local gt = Cerebro.db.global.rclc.groupButtons[storeKey]
							if gt then gt[idx] = value end
						end,
					}
					injectedCount = injectedCount + 1
				end
				
				-- Hook "Move Up" / "Move Down" buttons
				-- When buttons are reordered in RCLC, we must reorder our GP settings to match.
				local upBtn = groupNode["move_up" .. idx]
				if upBtn and not hookedButtons[upBtn] then
					local originalFunc = upBtn.func
					upBtn.func = function(...)
						if originalFunc then originalFunc(...) end
						if idx > 1 then
							local gt = Cerebro.db.global.rclc.groupButtons[storeKey]
							if gt then
								local temp = gt[idx]
								gt[idx] = gt[idx - 1]
								gt[idx - 1] = temp
							end
						end
					end
					hookedButtons[upBtn] = true
				end
				
				local downBtn = groupNode["move_down" .. idx]
				if downBtn and not hookedButtons[downBtn] then
					local originalFunc = downBtn.func
					downBtn.func = function(...)
						if originalFunc then originalFunc(...) end
						local gt = Cerebro.db.global.rclc.groupButtons[storeKey]
						if gt then
							local temp = gt[idx]
							gt[idx] = gt[idx + 1]
							gt[idx + 1] = temp
						end
					end
					hookedButtons[downBtn] = true
				end
			end
		end
	end
	
	-- Refresh options if we injected something new
	if injectedCount > 0 then
		LibStub("AceConfigRegistry-3.0"):NotifyChange(RCLC_ADDON_NAME)
	end
end

-- Look up the configured GP value for a given response text
function Bridge:GetRCLCResponseGP(response)
	if not response then return nil end
	local RCLC = LibStub("AceAddon-3.0"):GetAddon(RCLC_ADDON_NAME, true)
	if not RCLC then return nil end
	
	-- Scan all active button groups
	local buttonGroups = CopyTable(RCLC.db.profile.enabledButtons or {})
	buttonGroups.buttonOptions = true
	
	for groupName in pairs(buttonGroups) do
		local entryName = (groupName == "buttonOptions") and "default" or groupName
		local buttons = RCLC.db.profile.buttons and RCLC.db.profile.buttons[entryName]
		if buttons then
			for i = 1, (buttons.numButtons or 0) do
				-- Match response against Button Text OR Response Text
				local btnText = buttons[i] and buttons[i].text
				local respEntry = RCLC.db.profile.responses and RCLC.db.profile.responses[entryName]
				local respText = respEntry and respEntry[i] and respEntry[i].text
				
				if (btnText and btnText == response) or (respText and respText == response) then
					local cfg = Cerebro.db.global.rclc.groupButtons[entryName]
					if cfg and cfg[i] then
						return cfg[i]
					end
				end
			end
		end
	end
	return nil
end

--[[=============================================
	GUI Injection (Officer)
===============================================]]

function Bridge:InjectOfficerColumns()
	local RCLootCouncil = LibStub("AceAddon-3.0"):GetAddon("RCLootCouncil", true)
	if not RCLootCouncil then return end
	
	local RCVotingFrame = RCLootCouncil:GetModule("RCVotingFrame", true)
	if not RCVotingFrame then return end
	
	-- Check for existing
	for _, col in ipairs(RCVotingFrame.scrollCols or {}) do
		if col.colName == "pr" then return end
	end
	
	local function SetCellPR(rowFrame, frame, data, cols, row, realrow, column, fShow, table, ...)
		local name = data[realrow].name
		local player = Cerebro.Player:New(name)
		local pr = player:GetPR() or 0
		
		frame.text:SetText(string.format("%.2f", pr))
		frame.text:SetTextColor(1, 0.82, 0, 1) -- Gold
		
		data[realrow].cols[column].value = pr
	end
	
	-- Locate the response column index once at injection time
	-- (used as first tiebreaker when PR values are equal)
	local responseColIdx
	for i, col in ipairs(RCVotingFrame.scrollCols) do
		if col.colName == "response" then
			responseColIdx = i
			break
		end
	end

	local prColumn = {
		name = "PR",
		DoCellUpdate = SetCellPR,
		colName = "pr",
		width = 45,
		align = "CENTER",
		defaultsort = "dsc",
		sort = function(table, rowa, rowb, sortby)
			local v1 = table[rowa].cols[sortby].value
			local v2 = table[rowb].cols[sortby].value
			if v1 == v2 then
				-- Tiebreaker 1: RCLC response rank (lower index = higher priority)
				if responseColIdx then
					local r1 = table[rowa].cols[responseColIdx].value
					local r2 = table[rowb].cols[responseColIdx].value
					if r1 ~= r2 then
						return (r1 or 0) < (r2 or 0)
					end
				end
				-- Tiebreaker 2: alphabetical player name (deterministic)
				return (table[rowa].name or "") < (table[rowb].name or "")
			end
			return v1 < v2
		end,
	}
	
	-- Insert at position 5 (After Role/Class usually)
	-- Verify scrollCols exists
	if RCVotingFrame.scrollCols then
		table.insert(RCVotingFrame.scrollCols, 5, prColumn)
		Cerebro:Debug("Injected PR column into RCLC.")
	end
end

--[[=============================================
	GUI Injection (Candidate)
===============================================]]

function Bridge:InjectCandidateLabel()
	-- Hook RCLootFrame (The window candidates see)
	local RCLootFrame = RCLootCouncil_LootFrame -- Global name often used
	
	if not RCLootFrame and _G["RCLootFrame"] then
		RCLootFrame = _G["RCLootFrame"]
	end
	
	if not RCLootFrame then
		-- RCLC frame not found yet. It's usually created on demand.
		-- We can try to hook AceAddon:Enable/OnEnable for RCLC or just do a lazy init
		-- For now, just return. The Timer check or OnShow hook elsewhere might be better.
		return 
	end
	
	-- Assuming we found the frame or can attach to it.
	-- For safety, let's create a standalone frame that anchors to it whenever it's shown.
	-- Or simpler: Add an "Overlay" frame.
	
	if not self.prLabelFrame then
		local f = CreateFrame("Frame", "CerebroPRLabel", UIParent)
		f:SetFrameStrata("DIALOG")
		f:SetSize(100, 20)
		
		local t = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
		t:SetPoint("CENTER")
		t:SetText("PR: ---")
		f.text = t
		
		self.prLabelFrame = f
		f:Hide()
		
		-- Simple update script to show/hide with RCLC
		f:SetScript("OnUpdate", function(self, elapsed)
			-- Try to find frame if we lost it or it wasn't there
			if not RCLootFrame and _G["RCLootFrame"] then
				RCLootFrame = _G["RCLootFrame"]
			end

			if RCLootFrame and RCLootFrame:IsVisible() then
				self:Show()
				self:ClearAllPoints()
				self:SetPoint("TOPRIGHT", RCLootFrame, "TOPRIGHT", -25, -50)
				
				local p = Cerebro.Player:New("player")
				self.text:SetText(string.format("Your PR: %.2f", p:GetPR()))
			else
				self:Hide()
			end
		end)
	end
end

return Bridge
