local DR = select(2, ...)
local constants = DR.Modules["Core.Constants"]

local DeferredTrade = {}
local TIMEOUT_SWEEP_SECONDS = 30

local validStatus = {
  [constants.DEFERRED_TRADE.STATUS.TO_TRADE] = true,
  [constants.DEFERRED_TRADE.STATUS.TRADED] = true,
  [constants.DEFERRED_TRADE.STATUS.TIMEOUT] = true,
  [constants.DEFERRED_TRADE.STATUS.CANCELLED] = true,
}

local function newTradeId()
  local ts = GetServerTime and GetServerTime() or time()
  return string.format("trade-%d-%d", ts, math.random(1000, 9999))
end

local function normalizeName(name)
  if not name or name == "" then
    return nil
  end

  local out = tostring(name):gsub("%(%*%)", "")
  if Ambiguate then
    out = Ambiguate(out, "none")
  end
  out = out:gsub("%s+", "")
  return string.lower(out)
end

local function itemKey(itemLink)
  if not itemLink then
    return nil
  end

  if C_Item and C_Item.GetItemInfoInstant then
    local itemId = select(1, C_Item.GetItemInfoInstant(itemLink))
    if itemId then
      return "id:" .. tostring(itemId)
    end
  end

  return "link:" .. string.lower(tostring(itemLink))
end

function DeferredTrade:OnInitialize()
  DR.db.global.deferredTrades = DR.db.global.deferredTrades or {}
  DR.db.global.deferredTradeOrder = DR.db.global.deferredTradeOrder or {}
  self.timeoutSweepTimer = nil
end

function DeferredTrade:OnEnable()
  if self.timeoutSweepTimer then
    DR:CancelTimer(self.timeoutSweepTimer, true)
    self.timeoutSweepTimer = nil
  end

  self.timeoutSweepTimer = DR:ScheduleRepeatingTimer(function()
    self:RunTimeoutSweep()
  end, TIMEOUT_SWEEP_SECONDS)
end

function DeferredTrade:OnDisable()
  if self.timeoutSweepTimer then
    DR:CancelTimer(self.timeoutSweepTimer, true)
    self.timeoutSweepTimer = nil
  end
end

function DeferredTrade:Add(itemLink, sessionId, winnerGuid)
  local id = newTradeId()
  local entry = {
    id = id,
    itemLink = itemLink,
    sessionId = sessionId,
    winnerGuid = winnerGuid,
    status = constants.DEFERRED_TRADE.STATUS.TO_TRADE,
    createdAt = time(),
    updatedAt = time(),
  }

  DR.db.global.deferredTrades[id] = entry
  table.insert(DR.db.global.deferredTradeOrder, id)

  local audit = DR.Modules["Services.Audit"]
  if audit then
    audit:Append("DEFERRED_TRADE_ADD", {
      id = id,
      sessionId = sessionId,
      winnerGuid = winnerGuid,
      itemLink = itemLink,
    })
  end

  DR:SendMessage("DR_DEFERRED_TRADE_ADDED", entry)

  return id, entry
end

function DeferredTrade:SetStatus(id, status, reason)
  if not validStatus[status] then
    return false, "invalid_status"
  end

  local entry = DR.db.global.deferredTrades[id]
  if not entry then
    return false, "not_found"
  end

  entry.status = status
  entry.reason = reason
  entry.updatedAt = time()

  local audit = DR.Modules["Services.Audit"]
  if audit then
    audit:Append("DEFERRED_TRADE_STATUS", {
      id = id,
      status = status,
      reason = reason,
    })
  end

  DR:SendMessage("DR_DEFERRED_TRADE_UPDATED", entry)

  return true
end

function DeferredTrade:Get(id)
  return DR.db.global.deferredTrades[id]
end

--- Re-targets a pending trade when the session winner changes.
function DeferredTrade:SetWinner(id, winnerGuid)
  local entry = DR.db.global.deferredTrades[id]
  if not entry then
    return false, "not_found"
  end

  entry.winnerGuid = winnerGuid
  entry.updatedAt = time()

  local audit = DR.Modules["Services.Audit"]
  if audit then
    audit:Append("DEFERRED_TRADE_REWINNER", {
      id = id,
      winnerGuid = winnerGuid,
    })
  end

  DR:SendMessage("DR_DEFERRED_TRADE_UPDATED", entry)
  return true
end

function DeferredTrade:GetByStatus(status)
  local out = {}
  for _, id in ipairs(DR.db.global.deferredTradeOrder) do
    local entry = DR.db.global.deferredTrades[id]
    if entry and entry.status == status then
      out[#out + 1] = entry
    end
  end
  return out
end

function DeferredTrade:GetOrdered()
  local out = {}
  for _, id in ipairs(DR.db.global.deferredTradeOrder) do
    local entry = DR.db.global.deferredTrades[id]
    if entry then
      out[#out + 1] = entry
    end
  end
  return out
end

function DeferredTrade:GetFirstByStatus(status)
  for _, id in ipairs(DR.db.global.deferredTradeOrder) do
    local entry = DR.db.global.deferredTrades[id]
    if entry and entry.status == status then
      return entry
    end
  end
  return nil
end

--- First still-pending trade entry for a session, if any.
function DeferredTrade:GetPendingBySession(sessionId)
  if not sessionId then
    return nil
  end
  for _, id in ipairs(DR.db.global.deferredTradeOrder) do
    local entry = DR.db.global.deferredTrades[id]
    if entry and entry.sessionId == sessionId and entry.status == constants.DEFERRED_TRADE.STATUS.TO_TRADE then
      return entry
    end
  end
  return nil
end

--- All pending entries whose winner matches the given display name.
function DeferredTrade:GetPendingForTarget(targetDisplay)
  local out = {}
  local targetNorm = normalizeName(targetDisplay)
  if not targetNorm then
    return out
  end

  local identity = DR:Require("Services.Identity")
  for _, id in ipairs(DR.db.global.deferredTradeOrder) do
    local entry = DR.db.global.deferredTrades[id]
    if entry and entry.status == constants.DEFERRED_TRADE.STATUS.TO_TRADE then
      local winnerDisplay = identity:GetDisplayByGuid(entry.winnerGuid) or entry.winnerGuid
      if normalizeName(winnerDisplay) == targetNorm then
        out[#out + 1] = entry
      end
    end
  end
  return out
end

function DeferredTrade:GetStatusCounts()
  local counts = {
    [constants.DEFERRED_TRADE.STATUS.TO_TRADE] = 0,
    [constants.DEFERRED_TRADE.STATUS.TRADED] = 0,
    [constants.DEFERRED_TRADE.STATUS.TIMEOUT] = 0,
    [constants.DEFERRED_TRADE.STATUS.CANCELLED] = 0,
  }

  for _, id in ipairs(DR.db.global.deferredTradeOrder) do
    local entry = DR.db.global.deferredTrades[id]
    if entry and counts[entry.status] ~= nil then
      counts[entry.status] = counts[entry.status] + 1
    end
  end

  return counts
end

function DeferredTrade:FindTradeMatch(itemLink, tradeTargetDisplay)
  local matchKey = itemKey(itemLink)
  if not matchKey then
    return nil, nil, "no_item"
  end

  local identity = DR:Require("Services.Identity")
  local targetNorm = normalizeName(tradeTargetDisplay)
  local fallbackId = nil
  local fallbackEntry = nil

  for _, id in ipairs(DR.db.global.deferredTradeOrder) do
    local entry = DR.db.global.deferredTrades[id]
    if entry and entry.status == constants.DEFERRED_TRADE.STATUS.TO_TRADE and itemKey(entry.itemLink) == matchKey then
      if targetNorm then
        local winnerDisplay = identity:GetDisplayByGuid(entry.winnerGuid) or entry.winnerGuid
        if normalizeName(winnerDisplay) == targetNorm then
          return id, entry, "item_and_target"
        end
      end

      if not fallbackEntry then
        fallbackId = id
        fallbackEntry = entry
      end
    end
  end

  if fallbackEntry then
    if targetNorm then
      return fallbackId, fallbackEntry, "item_only_fallback"
    end
    return fallbackId, fallbackEntry, "item_only"
  end

  return nil, nil, "no_match"
end

function DeferredTrade:MarkTradedByItem(itemLink, tradeTargetDisplay, reason)
  local id, _, matchReason = self:FindTradeMatch(itemLink, tradeTargetDisplay)
  if not id then
    return false, nil, matchReason
  end

  local suffix = tradeTargetDisplay and tradeTargetDisplay ~= "" and (":" .. tradeTargetDisplay) or ""
  local note = (reason or "auto_trade_complete") .. suffix
  local ok, err = self:SetStatus(id, constants.DEFERRED_TRADE.STATUS.TRADED, note)
  if not ok then
    return false, nil, err
  end

  return true, id, matchReason
end

function DeferredTrade:ProcessTimeouts(nowTs)
  local timeoutMinutes = tonumber(DR.db.profile.deferredTradeTimeoutMinutes) or 120
  timeoutMinutes = math.max(timeoutMinutes, 1)
  local timeoutSeconds = timeoutMinutes * 60

  local count = 0
  for _, id in ipairs(DR.db.global.deferredTradeOrder) do
    local entry = DR.db.global.deferredTrades[id]
    if entry and entry.status == constants.DEFERRED_TRADE.STATUS.TO_TRADE then
      local lastTs = tonumber(entry.updatedAt) or tonumber(entry.createdAt) or nowTs
      if nowTs - lastTs >= timeoutSeconds then
        local ok = self:SetStatus(id, constants.DEFERRED_TRADE.STATUS.TIMEOUT, "auto_timeout")
        if ok then
          count = count + 1
        end
      end
    end
  end

  return count
end

function DeferredTrade:RunTimeoutSweep()
  if not DR.db.profile.autoTimeoutDeferredTrades then
    return 0
  end

  local nowTs = GetServerTime and GetServerTime() or time()
  local count = self:ProcessTimeouts(nowTs)
  if count > 0 then
    local audit = DR.Modules["Services.Audit"]
    if audit then
      audit:Append("DEFERRED_TRADE_TIMEOUT_SWEEP", {
        count = count,
      })
    end
  end

  return count
end

DR:RegisterModule("Services.DeferredTrade", DeferredTrade)
