local _, addon = ...
local HLY = addon.HLY

local C_Timer = C_Timer
local CreateFrame = CreateFrame
local C_LFGList = C_LFGList
local GetRealZoneText = GetRealZoneText
local IsInGroup = IsInGroup
local ipairs = ipairs
local pairs = pairs
local strfind = strfind
local time = time

local eventFrame = CreateFrame("Frame")
local JOIN_REMINDER_PENDING_TIMEOUT = 20

local function NormalizeStatus(status)
    if type(status) ~= "string" then
        return nil
    end

    local ok, normalizedStatus = pcall(string.lower, status)
    if ok then
        return normalizedStatus
    end

    return nil
end

local function IsInviteLikeStatus(status)
    local normalizedStatus = NormalizeStatus(status)
    if not normalizedStatus then
        return false
    end

    return string.find(normalizedStatus, "invite", 1, true) ~= nil
end

local function IsTerminalApplicationStatus(status)
    local normalizedStatus = NormalizeStatus(status)
    if not normalizedStatus then
        return false
    end

    return string.find(normalizedStatus, "declin", 1, true) ~= nil
        or string.find(normalizedStatus, "cancel", 1, true) ~= nil
        or string.find(normalizedStatus, "fail", 1, true) ~= nil
        or string.find(normalizedStatus, "timeout", 1, true) ~= nil
        or string.find(normalizedStatus, "abandon", 1, true) ~= nil
end

local function ShouldCachePremadeStatus(searchResultID, newStatus, oldStatus)
    if not searchResultID then
        return false
    end

    if IsTerminalApplicationStatus(newStatus) then
        return false
    end

    if IsInviteLikeStatus(newStatus) or IsInviteLikeStatus(oldStatus) then
        return true
    end

    return true
end

function HLY:ScheduleJoinReminderAnnouncementChecks()
    if not C_Timer then
        return
    end

    self.joinReminderAnnounceTimers = self.joinReminderAnnounceTimers or {}

    for _, timer in ipairs(self.joinReminderAnnounceTimers) do
        timer:Cancel()
    end

    self.joinReminderAnnounceTimers = {
        C_Timer.NewTimer(0.3, function() self:AnnounceJoinReminder() end),
        C_Timer.NewTimer(1.0, function() self:AnnounceJoinReminder() end),
        C_Timer.NewTimer(2.0, function() self:AnnounceJoinReminder() end),
    }
end

local function SafeGetSearchResultInfo(searchResultID)
    if not C_LFGList or not C_LFGList.GetSearchResultInfo then
        return nil, nil
    end

    local ok, info = pcall(C_LFGList.GetSearchResultInfo, searchResultID)
    if not ok or type(info) ~= "table" then
        return nil, nil
    end

    return info, searchResultID
end

local function SafeGetActivityName(searchResultID)
    local info = SafeGetSearchResultInfo(searchResultID)
    if not info then
        return nil
    end

    local activityIDs = {}
    if type(info.activityIDs) == "table" then
        for _, activityID in pairs(info.activityIDs) do
            if activityID then
                activityIDs[#activityIDs + 1] = activityID
            end
        end
    elseif info.activityID then
        activityIDs[1] = info.activityID
    end

    if #activityIDs == 0 then
        return nil
    end

    for _, activityID in ipairs(activityIDs) do
        if C_LFGList.GetActivityFullName then
            local fullNameOk, fullName = pcall(C_LFGList.GetActivityFullName, activityID)
            if fullNameOk and type(fullName) == "string" and fullName ~= "" then
                return fullName
            end
        end

        if C_LFGList.GetActivityInfoTable then
            local tableOk, activityInfo = pcall(C_LFGList.GetActivityInfoTable, activityID)
            if tableOk and type(activityInfo) == "table" then
                local fullName = activityInfo.fullName or activityInfo.shortName
                if type(fullName) == "string" and fullName ~= "" then
                    return fullName
                end
            end
        end

        if C_LFGList.GetActivityInfo then
            local activityOk, fullName, shortName = pcall(C_LFGList.GetActivityInfo, activityID)
            if activityOk then
                local activityName = fullName or shortName
                if type(activityName) == "string" and activityName ~= "" then
                    return activityName
                end
            end
        end
    end

    return nil
end

local function SafeGetListingDetails(searchResultID)
    local info = SafeGetSearchResultInfo(searchResultID)
    if not info then
        return nil
    end

    return {
        searchResultID = searchResultID,
        groupName = info.name or info.title or info.leaderName or "Unknown Group",
        activity = SafeGetActivityName(searchResultID),
    }
end

local function SafeGetApplicationSearchResultID(applicationID)
    if not C_LFGList or not C_LFGList.GetApplicationInfo then
        return nil, nil
    end

    local ok, info = pcall(C_LFGList.GetApplicationInfo, applicationID)
    if not ok then
        return nil, nil
    end

    if type(info) == "table" then
        return info.searchResultID, info.applicationStatus or info.status
    end

    return info, nil
end

local function SafeGetBestApplicationListing()
    if not C_LFGList or not C_LFGList.GetApplications then
        return nil
    end

    local ok, applications = pcall(C_LFGList.GetApplications)
    if not ok or type(applications) ~= "table" then
        return nil
    end

    local fallback

    for _, applicationID in ipairs(applications) do
        local searchResultID, status = SafeGetApplicationSearchResultID(applicationID)
        if searchResultID then
            local details = SafeGetListingDetails(searchResultID)
            if details then
                fallback = fallback or details
                local normalizedStatus = NormalizeStatus(status)
                if normalizedStatus and string.find(normalizedStatus, "invite", 1, true) then
                    return details
                end
            end
        end
    end

    return fallback
end

local function SafeGetJoinedActivityFromApplications()
    local details = SafeGetBestApplicationListing()
    return details and details.activity or nil
end

local function SafeGetPremadePendingData()
    local details = SafeGetBestApplicationListing()
    if not details then
        return nil
    end

    return {
        groupName = details.groupName,
        activity = details.activity,
        source = "premade",
        timestamp = time(),
    }
end

function HLY:CreateJoinReminderFrame()
    if self.joinReminderFrame then
        return self.joinReminderFrame
    end

    local frame = CreateFrame("Frame", nil, UIParent)
    frame:SetSize(520, 64)
    frame:SetMovable(true)
    frame:EnableMouse(false)
    frame:RegisterForDrag("LeftButton")
    frame:SetClampedToScreen(true)
    frame:SetFrameStrata("DIALOG")

    local text = frame:CreateFontString(nil, "OVERLAY")
    text:SetPoint("CENTER")
    text:SetJustifyH("CENTER")
    text:SetJustifyV("MIDDLE")
    text:SetWidth(900)
    frame.text = text

    frame:SetScript("OnDragStart", function(movingFrame)
        if self.joinReminderMoveMode then
            movingFrame:StartMoving()
        end
    end)
    frame:SetScript("OnDragStop", function(movingFrame)
        movingFrame:StopMovingOrSizing()
        local point, _, _, x, y = movingFrame:GetPoint(1)
        self.db.joinReminder.point = point or "CENTER"
        self.db.joinReminder.xOffset = x or 0
        self.db.joinReminder.yOffset = y or 0
    end)
    frame:SetScript("OnMouseUp", function(_, button)
        if button == "RightButton" then
            self.joinReminderTestVisible = nil
            self:SetJoinReminderMoveMode(false)
        end
    end)

    frame:Hide()
    self.joinReminderFrame = frame
    self:UpdateJoinReminderFrameStyle()
    return frame
end

function HLY:UpdateJoinReminderFrameBounds()
    local frame = self.joinReminderFrame
    if not frame or not frame.text then
        return
    end

    local textWidth = frame.text:GetStringWidth() or 0
    local textHeight = frame.text:GetStringHeight() or 0
    frame:SetSize(math.max(textWidth + 40, 120), math.max(textHeight + 24, 28))
end

function HLY:UpdateJoinReminderFrameStyle()
    local frame = self:CreateJoinReminderFrame()
    local db = self.db and self.db.joinReminder
    if not frame or not db then
        return
    end

    frame:ClearAllPoints()
    frame:SetPoint(db.point or "CENTER", UIParent, db.point or "CENTER", db.xOffset or 0, db.yOffset or 180)

    local color = db.text and db.text.color or { r = 1, g = 0.82, b = 0.2 }
    self:ApplyFont(frame.text, db.text.font, db.text.fontSize, "SHADOWOUTLINE")
    frame.text:SetTextColor(color.r or 1, color.g or 1, color.b or 1)
    self:UpdateJoinReminderFrameBounds()
end

function HLY:SetJoinReminderMoveMode(enabled)
    self.joinReminderMoveMode = enabled and true or false
    local frame = self:CreateJoinReminderFrame()
    self:UpdateJoinReminderFrameStyle()

    if self.joinReminderMoveMode then
        frame:EnableMouse(true)
        frame.text:SetText("Join Reminder Test\nDrag this text to move it.")
        self:UpdateJoinReminderFrameBounds()
        frame:SetAlpha(1)
        frame:Show()
        return
    end

    frame:EnableMouse(false)
    if self.joinReminderTestVisible then
        return
    end
    frame:Hide()
end

function HLY:ResetJoinReminderPosition()
    if not self.db or not self.db.joinReminder then
        return
    end

    self.db.joinReminder.point = "CENTER"
    self.db.joinReminder.xOffset = 0
    self.db.joinReminder.yOffset = 180
    self:UpdateJoinReminderFrameStyle()
end

function HLY:ResetJoinReminderColor()
    if not self.db or not self.db.joinReminder or not self.db.joinReminder.text then
        return
    end

    local defaults = addon.P.hlyqol.joinReminder.text.color
    local color = self.db.joinReminder.text.color
    color.r = defaults.r
    color.g = defaults.g
    color.b = defaults.b
    self:UpdateJoinReminderFrameStyle()
end

function HLY:ShowJoinReminderScreenMessage(message, duration)
    local db = self.db and self.db.joinReminder
    if not db or not db.showScreen then
        return
    end

    local frame = self:CreateJoinReminderFrame()
    self.joinReminderTestVisible = nil
    self:UpdateJoinReminderFrameStyle()
    frame.text:SetText(message or "")
    self:UpdateJoinReminderFrameBounds()
    frame:SetAlpha(1)
    frame:Show()

    if self.joinReminderHideTimer then
        self.joinReminderHideTimer:Cancel()
        self.joinReminderHideTimer = nil
    end

    self.joinReminderHideTimer = C_Timer.NewTimer(duration or db.duration or 5, function()
        if self.joinReminderMoveMode then
            return
        end
        frame:Hide()
    end)
end

function HLY:ShowJoinReminderTestFrame()
    self.joinReminderTestVisible = true
    local duration = (self.db and self.db.joinReminder and self.db.joinReminder.duration) or 5
    self:ShowJoinReminderScreenMessage("Joined: Example Group - Mythic+", duration)
    C_Timer.After(duration, function()
        self.joinReminderTestVisible = nil
        if self.joinReminderMoveMode then
            self:SetJoinReminderMoveMode(true)
        end
    end)
end

function HLY:BuildJoinReminderMessage()
    local cached = self.joinReminderPendingData or {}
    local groupName = cached.groupName or (GetRealZoneText and GetRealZoneText()) or "Unknown Group"
    local activity = cached.activity

    if (not activity or activity == "") then
        local details = SafeGetBestApplicationListing()
        if details then
            groupName = cached.groupName or details.groupName or groupName
            activity = details.activity
        else
            activity = SafeGetJoinedActivityFromApplications()
        end

        if self.joinReminderPendingData then
            self.joinReminderPendingData.groupName = groupName
            self.joinReminderPendingData.activity = activity
        end
    end

    if activity and activity ~= "" then
        return string.format("%s: Joined: %s - %s", addon.displayName, groupName, activity)
    end

    return string.format("%s: Joined: %s", addon.displayName, groupName)
end

function HLY:BuildJoinReminderDisplayMessage()
    local cached = self.joinReminderPendingData or {}
    local groupName = cached.groupName or (GetRealZoneText and GetRealZoneText()) or "Unknown Group"
    local activity = cached.activity

    if (not activity or activity == "") then
        local details = SafeGetBestApplicationListing()
        if details then
            groupName = cached.groupName or details.groupName or groupName
            activity = details.activity
        end
    end

    if activity and activity ~= "" then
        return string.format("Joined: %s - %s", groupName, activity)
    end

    return string.format("Joined: %s", groupName)
end

function HLY:CacheJoinReminderInvite(inviterName)
    self.joinReminderPendingData = {
        groupName = inviterName or "Unknown Group",
        activity = nil,
        source = "invite",
        timestamp = time(),
    }
end

function HLY:CacheJoinReminderLFG(searchResultID, newStatus, oldStatus, groupName)
    if not ShouldCachePremadeStatus(searchResultID, newStatus, oldStatus) then
        return
    end

    local details = SafeGetBestApplicationListing()
    if details then
        self.joinReminderPendingData = {
            groupName = details.groupName,
            activity = details.activity,
            source = "premade",
            timestamp = time(),
        }
        return
    end

    if searchResultID then
        self.joinReminderPendingData = {
            groupName = groupName or "Unknown Group",
            activity = SafeGetActivityName(searchResultID),
            source = "premade",
            timestamp = time(),
        }
    end

    if self.joinReminderPendingData then
        self:ScheduleJoinReminderAnnouncementChecks()
    end
end

function HLY:HandleJoinReminderJoinedGroup(searchResultID, groupName)
    local details = searchResultID and SafeGetListingDetails(searchResultID) or nil
    self.joinReminderPendingData = {
        groupName = (details and details.groupName) or groupName or "Unknown Group",
        activity = details and details.activity or nil,
        source = "premade",
        timestamp = time(),
    }

    self:ScheduleJoinReminderAnnouncementChecks()
    self:AnnounceJoinReminder()
end

function HLY:AnnounceJoinReminder()
    if not self.db or not self.db.joinReminder or not self.db.joinReminder.enabled then
        return
    end

    if not IsInGroup() then
        return
    end

    if not self.joinReminderPendingData or self.joinReminderPendingData.source ~= "premade" then
        return
    end

    local pendingAge = time() - (self.joinReminderPendingData.timestamp or 0)
    if pendingAge > JOIN_REMINDER_PENDING_TIMEOUT then
        self.joinReminderPendingData = nil
        return
    end

    local now = time()
    if self.joinReminderLastAnnounce and (now - self.joinReminderLastAnnounce) < 5 then
        return
    end

    local message = self:BuildJoinReminderMessage()
    if self.db.joinReminder.showChat then
        print(message)
    end
    self:ShowJoinReminderScreenMessage(self:BuildJoinReminderDisplayMessage(), self.db.joinReminder.duration)
    self.joinReminderLastAnnounce = now
    self.joinReminderPendingData = nil
end

function HLY:HandleJoinReminderRosterUpdate()
    local isInGroup = IsInGroup()

    if isInGroup and not self.joinReminderWasInGroup then
        C_Timer.After(0.5, function()
            self:AnnounceJoinReminder()
        end)
    end

    self.joinReminderWasInGroup = isInGroup
    if not isInGroup then
        self.joinReminderPendingData = nil
    end
end

function HLY:HandleJoinReminderEvent(event, ...)
    if event == "PLAYER_ENTERING_WORLD" then
        self.joinReminderWasInGroup = IsInGroup()
        return
    end

    if event == "LFG_LIST_APPLICATION_STATUS_UPDATED" then
        self:CacheJoinReminderLFG(...)
        return
    end

    if event == "LFG_LIST_JOINED_GROUP" then
        self:HandleJoinReminderJoinedGroup(...)
        return
    end

    if event == "GROUP_ROSTER_UPDATE" then
        self:HandleJoinReminderRosterUpdate()
    end
end

function HLY:SetupJoinReminder()
    if self.joinReminderSetup then
        return
    end

    self:CreateJoinReminderFrame()
    self:UpdateJoinReminderFrameStyle()

    eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
    eventFrame:RegisterEvent("LFG_LIST_APPLICATION_STATUS_UPDATED")
    eventFrame:RegisterEvent("LFG_LIST_JOINED_GROUP")
    eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
    eventFrame:SetScript("OnEvent", function(_, event, ...)
        self:HandleJoinReminderEvent(event, ...)
    end)

    self.joinReminderWasInGroup = IsInGroup()
    self.joinReminderSetup = true
end
