local _, addonTable = ...
local WipedQoL = addonTable.WipedQoL
local TRANSLATE = addonTable.TRANSLATE or function(msg) return msg end

local COPPER_PER_GOLD = 10000

local DEBUG_TEST_CHARACTERS = {
    { name = "Aeloria", realm = "WQOL-Test", classFile = "MAGE", excluded = true },
    { name = "Brommgar", realm = "WQOL-Test", classFile = "WARRIOR", excluded = true },
    { name = "Caelindra", realm = "WQOL-Test", classFile = "DRUID" },
    { name = "Dornhelm", realm = "WQOL-Test", classFile = "PALADIN" },
    { name = "Elyndra", realm = "WQOL-Test", classFile = "PRIEST", excluded = true },
    { name = "Faelric", realm = "WQOL-Test", classFile = "ROGUE" },
    { name = "Grimwold", realm = "WQOL-Test", classFile = "DEATHKNIGHT" },
    { name = "Halarin", realm = "WQOL-Test", classFile = "HUNTER" },
    { name = "Ilyrielle", realm = "WQOL-Test", classFile = "DEMONHUNTER" },
    { name = "Jorvask", realm = "WQOL-Test", classFile = "SHAMAN" },
    { name = "Kaelthorn", realm = "WQOL-Test", classFile = "WARLOCK" },
    { name = "Lunessa", realm = "WQOL-Test", classFile = "MONK" },
}

local DEBUG_TEST_CHARACTER_KEYS = {}
for _, character in ipairs(DEBUG_TEST_CHARACTERS) do
    DEBUG_TEST_CHARACTER_KEYS[string.format("%s - %s", character.name, character.realm)] = true
end

local function FormatMoney(copper)
    copper = math.max(tonumber(copper) or 0, 0)

    if C_CurrencyInfo and C_CurrencyInfo.GetCoinTextureString then
        return C_CurrencyInfo.GetCoinTextureString(copper)
    end

    local gold = math.floor(copper / COPPER_PER_GOLD)
    local silver = math.floor((copper % COPPER_PER_GOLD) / 100)
    local copperOnly = copper % 100

    return string.format("%dg %ds %dc", gold, silver, copperOnly)
end

local function GetWarbandBankType()
    return Enum and Enum.BankType and Enum.BankType.Account or nil
end

local function CanMoveWarbandGold(bankType)
    if not C_Bank or not bankType then
        return false
    end

    if not C_Bank.DoesBankTypeSupportMoneyTransfer or not C_Bank.DoesBankTypeSupportMoneyTransfer(bankType) then
        return false
    end

    return C_Bank.CanUseBank and C_Bank.CanUseBank(bankType)
end

local function CanUseWarbandBank(bankType)
    if not C_Bank or not bankType then
        return false
    end

    return C_Bank.CanUseBank and C_Bank.CanUseBank(bankType)
end

local function ClampGoldTarget(value)
    value = tonumber(value) or 0
    if value < 0 then
        return 0
    end

    return value
end

local function ToCopper(gold)
    return math.floor((ClampGoldTarget(gold) * COPPER_PER_GOLD) + 0.5)
end

local function GetCurrentCharacterKey()
    local name = UnitName and UnitName("player")
    local realm = GetRealmName and GetRealmName()

    if not name or name == "" then
        return nil
    end

    if realm and realm ~= "" then
        return string.format("%s - %s", name, realm)
    end

    return name
end

local function FormatCharacterLabel(name, realm, fallback)
    if name and name ~= "" and realm and realm ~= "" then
        return string.format("%s-%s", name, realm)
    end

    return fallback and tostring(fallback):gsub("%s+%-%s+", "-") or fallback
end

local function ParseCharacterKey(key)
    if type(key) ~= "string" then
        return nil, nil
    end

    local name, realm = key:match("^(.+)%s+%-%s+(.+)$")
    return name, realm
end

local function TransferDeposit(bankType, amount)
    if amount <= 0 or not C_Bank.CanDepositMoney or not C_Bank.DepositMoney then
        return false
    end

    if not C_Bank.CanDepositMoney(bankType) then
        return false
    end

    C_Bank.DepositMoney(bankType, amount)
    return true
end

local function TransferWithdrawal(bankType, amount)
    if amount <= 0 or not C_Bank.CanWithdrawMoney or not C_Bank.WithdrawMoney then
        return false
    end

    if not C_Bank.CanWithdrawMoney(bankType) then
        return false
    end

    C_Bank.WithdrawMoney(bankType, amount)
    return true
end

function WipedQoL:GetAutoBankGoldSettings()
    local modules = self.db and self.db.profile and self.db.profile.modules
    if not modules then
        return {}
    end

    if type(modules.autoBankGold) ~= "table" then
        modules.autoBankGold = {
            enabled = false,
            targetGold = 10000,
        }
    end

    local settings = modules.autoBankGold
    settings.enabled = settings.enabled == true
    settings.targetGold = tonumber(settings.targetGold) or 0
    return settings
end

function WipedQoL:GetWarboundBankItemSettings()
    local modules = self.db and self.db.profile and self.db.profile.modules
    if not modules then
        return {}
    end

    if type(modules.warboundBankItems) ~= "table" then
        modules.warboundBankItems = {
            enabled = false,
        }
    end

    local settings = modules.warboundBankItems
    settings.enabled = settings.enabled == true
    return settings
end

function WipedQoL:GetAutoBankGoldGlobalSettings()
    local global = self.db and self.db.global
    if not global then
        return {
            knownCharacters = {},
            autoBankGoldExcludedCharacters = {},
        }
    end

    if type(global.knownCharacters) ~= "table" then
        global.knownCharacters = {}
    end

    if type(global.autoBankGoldExcludedCharacters) ~= "table" then
        global.autoBankGoldExcludedCharacters = {}
    end

    if type(global.deletedKnownCharacters) ~= "table" then
        global.deletedKnownCharacters = {}
    end

    return global
end

function WipedQoL:RegisterKnownCharacter()
    local global = self:GetAutoBankGoldGlobalSettings()
    local key = GetCurrentCharacterKey()
    if not key then
        return
    end

    if global.deletedKnownCharacters and global.deletedKnownCharacters[key] then
        return
    end

    global.knownCharacters[key] = {
        name = UnitName and UnitName("player") or key,
        realm = GetRealmName and GetRealmName() or nil,
        classFile = select(2, UnitClass("player")),
        lastSeen = time and time() or nil,
    }
end

function WipedQoL:GetKnownCharacters()
    self:RegisterKnownCharacter()

    local global = self:GetAutoBankGoldGlobalSettings()
    local characters = {}

    for key, data in pairs(global.knownCharacters or {}) do
        if not (global.deletedKnownCharacters and global.deletedKnownCharacters[key]) then
            local parsedName, parsedRealm = ParseCharacterKey(key)
            local name = type(data) == "table" and data.name or parsedName
            local realm = type(data) == "table" and data.realm or parsedRealm
            name = name or parsedName
            realm = realm or parsedRealm

            characters[#characters + 1] = {
                key = key,
                label = FormatCharacterLabel(name, realm, key),
                name = name,
                realm = realm,
                classFile = type(data) == "table" and data.classFile or nil,
            }
        end
    end

    if self.debugTestDataEnabled then
        for _, character in ipairs(DEBUG_TEST_CHARACTERS) do
            local key = string.format("%s - %s", character.name, character.realm)
            if not (self.debugTestDataDeletedCharacters and self.debugTestDataDeletedCharacters[key]) then
                characters[#characters + 1] = {
                    key = key,
                    label = FormatCharacterLabel(character.name, character.realm, key),
                    name = character.name,
                    realm = character.realm,
                    classFile = character.classFile,
                }
            end
        end
    end

    table.sort(characters, function(left, right)
        local leftRealm = tostring(left.realm or "")
        local rightRealm = tostring(right.realm or "")
        if leftRealm ~= rightRealm then
            return leftRealm < rightRealm
        end

        return tostring(left.label) < tostring(right.label)
    end)

    return characters
end

function WipedQoL:IsAutoBankGoldCharacterExcluded(characterKey)
    local global = self:GetAutoBankGoldGlobalSettings()
    characterKey = characterKey or GetCurrentCharacterKey()
    if self.debugTestDataEnabled and characterKey and DEBUG_TEST_CHARACTER_KEYS[characterKey] then
        return self.debugTestDataExcludedCharacters and self.debugTestDataExcludedCharacters[characterKey] == true
    end

    local excludedCharacters = global.autoBankGoldExcludedCharacters
    return characterKey and excludedCharacters and excludedCharacters[characterKey] == true
end

function WipedQoL:SetAutoBankGoldCharacterExcluded(characterKey, excluded)
    if not characterKey then
        return
    end

    if self.debugTestDataEnabled and DEBUG_TEST_CHARACTER_KEYS[characterKey] then
        self.debugTestDataExcludedCharacters = self.debugTestDataExcludedCharacters or {}
        self.debugTestDataExcludedCharacters[characterKey] = excluded == true or nil
        return
    end

    local global = self:GetAutoBankGoldGlobalSettings()
    global.autoBankGoldExcludedCharacters[characterKey] = excluded == true or nil
end

function WipedQoL:DeleteKnownCharacter(characterKey)
    if not characterKey then
        return
    end

    if self.debugTestDataEnabled and DEBUG_TEST_CHARACTER_KEYS[characterKey] then
        self.debugTestDataExcludedCharacters = self.debugTestDataExcludedCharacters or {}
        self.debugTestDataExcludedCharacters[characterKey] = nil
        self.debugTestDataDeletedCharacters = self.debugTestDataDeletedCharacters or {}
        self.debugTestDataDeletedCharacters[characterKey] = true
        return
    end

    local global = self:GetAutoBankGoldGlobalSettings()
    global.deletedKnownCharacters[characterKey] = true
    global.knownCharacters[characterKey] = nil
    global.autoBankGoldExcludedCharacters[characterKey] = nil
end

function WipedQoL:SetDebugTestDataEnabled(enabled)
    local global = self:GetAutoBankGoldGlobalSettings()
    enabled = enabled == true
    self.debugTestDataEnabled = enabled
    self.debugTestDataExcludedCharacters = {}
    self.debugTestDataDeletedCharacters = {}

    for _, character in ipairs(DEBUG_TEST_CHARACTERS) do
        local key = string.format("%s - %s", character.name, character.realm)

        if global.knownCharacters[key] and global.knownCharacters[key].debugTestData == true then
            global.knownCharacters[key] = nil
        end
        global.autoBankGoldExcludedCharacters[key] = nil

        if enabled then
            self.debugTestDataExcludedCharacters[key] = character.excluded == true or nil
        end
    end

    global.debugTestDataEnabled = nil

    self:PrintDebug(enabled and TRANSLATE("Debug test data enabled.") or TRANSLATE("Debug test data disabled."))

    if self.mainWindow and self.SelectMainTab then
        self:SelectMainTab(self.activeTab or (self.db and self.db.profile and self.db.profile.window and self.db.profile.window.activeTab) or "general")
    end
end

function WipedQoL:SetAutoBankGoldTargetGold(value)
    local settings = self:GetAutoBankGoldSettings()
    settings.targetGold = math.floor(ClampGoldTarget(value) + 0.5)
end

function WipedQoL:SetupAutoBankGold()
    if self.autoBankGoldEventsRegistered then
        return
    end

    self.autoBankGoldEventsRegistered = true
    self:RegisterEvent("BANKFRAME_OPENED", "HandleAutoBankGoldOpened")
end

function WipedQoL:HandleAutoBankGoldOpened()
    if C_Timer then
        C_Timer.After(0, function()
            if WipedQoL.AutoSyncBankGold then
                WipedQoL:AutoSyncBankGold()
            end
            if WipedQoL.AutoDepositWarboundBankItems then
                WipedQoL:AutoDepositWarboundBankItems()
            end
        end)
        return
    end

    self:AutoSyncBankGold()
    self:AutoDepositWarboundBankItems()
end

function WipedQoL:AutoSyncBankGold()
    if self:IsInCombat() then
        return
    end

    local settings = self:GetAutoBankGoldSettings()
    if not settings.enabled then
        return
    end

    if self:IsAutoBankGoldCharacterExcluded() then
        return
    end

    local bankType = GetWarbandBankType()
    if not CanMoveWarbandGold(bankType) then
        return
    end

    local targetCopper = ToCopper(settings.targetGold)
    local playerMoney = GetMoney and GetMoney() or 0
    local difference = playerMoney - targetCopper

    if difference > 0 then
        if TransferDeposit(bankType, difference) then
            self:Print(string.format(TRANSLATE("Deposited %s."), FormatMoney(difference)))
        end
        return
    end

    if difference == 0 then
        return
    end

    local bankMoney = C_Bank.FetchDepositedMoney and C_Bank.FetchDepositedMoney(bankType) or 0
    local withdrawal = math.min(math.abs(difference), bankMoney)
    if TransferWithdrawal(bankType, withdrawal) then
        self:Print(string.format(TRANSLATE("Withdrew %s."), FormatMoney(withdrawal)))
    end
end

function WipedQoL:AutoDepositWarboundBankItems()
    if self:IsInCombat() then
        return
    end

    local settings = self:GetWarboundBankItemSettings()
    if not settings.enabled then
        return
    end

    local bankType = GetWarbandBankType()
    if not CanUseWarbandBank(bankType) then
        return
    end

    if not C_Bank or not C_Bank.AutoDepositItemsIntoBank then
        return
    end

    if CursorHasItem and CursorHasItem() then
        return
    end

    C_Bank.AutoDepositItemsIntoBank(bankType)
end
