InvoiceManager = {}
InvoiceManager_mt = Class(InvoiceManager)
InvoiceManager.dir = g_currentModDirectory
InvoiceManager.modName = g_currentModName

table.insert(FinanceStats.statNames, "invoices")
FinanceStats.statNameToIndex["invoices"] = #FinanceStats.statNames

function InvoiceManager.new()
  local self = setmetatable({}, InvoiceManager_mt)

  self.invoices = {}
  self.nextId = 1

  MoneyType.INVOICES = MoneyType.register("invoices", "cc_money_type_invoices")
  MoneyType.LAST_ID = MoneyType.LAST_ID + 1

  if g_currentMission:getIsServer() then
    g_messageCenter:subscribe(
      MessageType.PLAYER_CONNECTED,
      self.onPlayerConnected,
      self
    )
  end

  return self
end

function InvoiceManager:saveToXmlFile(xmlFile)
  if not g_currentMission:getIsServer() then return end

  local key = CustomContracts.SaveKey
  local count = 0

  for id, invoice in pairs(self.invoices) do
    local invKey = string.format("%s.invoice(%d)", key, count)

    setXMLInt(xmlFile, invKey .. "#id", invoice.id)
    setXMLString(xmlFile, invKey .. "#number", invoice.number or "")
    setXMLInt(xmlFile, invKey .. "#creatorFarmId", invoice.creatorFarmId)
    setXMLInt(xmlFile, invKey .. "#receiverFarmId", invoice.receiverFarmId or -1)
    setXMLString(xmlFile, invKey .. "#status", invoice.status or Invoice.STATUS.DRAFT)
    setXMLString(xmlFile, invKey .. "#currency", invoice.currency or "EUR")
    setXMLString(xmlFile, invKey .. "#title", invoice.title)
    setXMLString(xmlFile, invKey .. "#description", invoice.description or "")
    setXMLInt(xmlFile, invKey .. "#total", invoice.total)
    setXMLInt(xmlFile, invKey .. "#paid", invoice.paid or 0)
    setXMLInt(xmlFile, invKey .. "#remaining", invoice.remaining or 0) -- future: partial payments
    setXMLInt(xmlFile, invKey .. "#relatedContractId", invoice.relatedContractId or -1)
    setXMLBool(xmlFile, invKey .. "#autoGenerated", invoice.autoGenerated == true)
    setXMLString(xmlFile, invKey .. "#meta", invoice.meta or "")
    self:saveInvoiceLinesToXml(xmlFile, invKey, invoice.lines)

    count = count + 1
  end
end

function InvoiceManager:loadFromXmlFile(xmlFile)
  if not g_currentMission:getIsServer() then return end

  self.invoices = {}
  self.nextId = 1

  local key = CustomContracts.SaveKey
  local i = 0

  while true do
    local invoiceKey = string.format("%s.invoice(%d)", key, i)
    if not hasXMLProperty(xmlFile, invoiceKey) then
      break
    end

    local id                = getXMLInt(xmlFile, invoiceKey .. "#id")
    local number            = getXMLString(xmlFile, invoiceKey .. "#number")
    local creatorFarmId     = getXMLInt(xmlFile, invoiceKey .. "#creatorFarmId")
    local receiverFarmId    = getXMLInt(xmlFile, invoiceKey .. "#receiverFarmId")
    local status            = getXMLString(xmlFile, invoiceKey .. "#status")
    local currency          = getXMLString(xmlFile, invoiceKey .. "#currency")
    local title             = getXMLString(xmlFile, invoiceKey .. "#title")
    local description       = getXMLString(xmlFile, invoiceKey .. "#description")
    local total             = getXMLInt(xmlFile, invoiceKey .. "#total")
    local paid              = getXMLInt(xmlFile, invoiceKey .. "#paid")
    local remaining         = getXMLInt(xmlFile, invoiceKey .. "#remaining")
    local relatedContractId = getXMLInt(xmlFile, invoiceKey .. "#relatedContractId")
    local autoGenerated     = getXMLBool(xmlFile, invoiceKey .. "#autoGenerated")
    local meta              = getXMLString(xmlFile, invoiceKey .. "#meta")

    local invoice           = Invoice.new(
      id,
      creatorFarmId,
      receiverFarmId,
      status or Invoice.STATUS.DRAFT,
      title,
      description,
      total,
      paid,
      relatedContractId ~= -1 and relatedContractId or nil,
      autoGenerated
    )

    invoice.number          = number
    invoice.currency        = currency or "EUR"
    invoice.remaining       = remaining or 0
    invoice.meta            = meta ~= "" and meta or nil

    invoice.lines           = self:loadInvoiceLinesFromXml(xmlFile, invoiceKey)

    self.invoices[id]       = invoice
    self.nextId             = math.max(self.nextId or 1, id + 1)

    i                       = i + 1
  end

  self:syncInvoices()
end

function InvoiceManager:writeInitialClientState(streamId, connection)
  streamWriteInt32(streamId, self.nextId)

  local count = table.size(self.invoices)
  streamWriteInt32(streamId, count)

  for _, invoice in pairs(self.invoices) do
    invoice:writeStream(streamId)
  end
end

function InvoiceManager:readInitialClientState(streamId, connection)
  self.invoices = {}

  self.nextId = streamReadInt32(streamId)
  local count = streamReadInt32(streamId)

  for i = 1, count do
    local invoice = Invoice.newFromStream(streamId)
    self.invoices[invoice.id] = invoice
  end

  g_messageCenter:publish(MessageType.INVOICES_UPDATED)
end

function InvoiceManager:syncInvoices(connection)
  if not g_currentMission:getIsServer() then return end

  local event = SyncInvoicesEvent.new(self.invoices, self.nextId)

  if connection ~= nil then
    connection:sendEvent(event)
  else
    g_server:broadcastEvent(event, true)
  end
end

function InvoiceManager:onPlayerConnected(connection)
  if not g_currentMission:getIsServer() then return end
  if connection == nil then return end

  self:syncInvoices(connection)
end

function InvoiceManager:getOutboundInvoicesByCurrentFarm()
  local invoices = {}

  local farmId = g_currentMission:getFarmId();

  for _, invoice in pairs(self.invoices) do
    local isCreator = invoice.creatorFarmId == farmId

    if isCreator then
      table.insert(invoices, invoice)
    end
  end

  return invoices
end

function InvoiceManager:getInboundInvoicesByCurrentFarm()
  local invoices = {}

  local farmId = g_currentMission:getFarmId();
  if farmId == nil or farmId == 0 then
    return invoices
  end

  for _, invoice in pairs(self.invoices) do
    local isReceiver = invoice.receiverFarmId == farmId

    if isReceiver and invoice.status ~= Invoice.STATUS.DRAFT then
      table.insert(invoices, invoice)
    end
  end

  return invoices
end

function InvoiceManager:handleCreateRequest(farmId, payload)
  if not g_currentMission:getIsServer() then return end

  local id          = self.nextId
  self.nextId       = self.nextId + 1

  local invoice     = Invoice.new(
    id,
    farmId,
    payload.receiverFarmId,
    payload.status or Invoice.STATUS.DRAFT,
    payload.title,
    payload.description,
    payload.total,
    0,
    payload.relatedContractId ~= -1 and payload.relatedContractId or nil,
    payload.autoGenerated == true
  )

  -- Generate readable invoice number
  invoice.number    = string.format("CC-INV-%06d", id)
  invoice.lines     = payload.lines or {}

  self.invoices[id] = invoice

  -- If the invoice is related to a contract, set the contract status to invoiced
  if payload.relatedContractId ~= nil and payload.relatedContractId ~= -1 then
    local contract = g_currentMission.CustomContracts.ContractManager.contracts[payload.relatedContractId]
    contract.status = CustomContract.STATUS.INVOICED
  end

  -- Update all clients with the new invoice and contract status
  self:syncInvoices()
  g_currentMission.CustomContracts.ContractManager:syncContracts()
end

function InvoiceManager:handleSendRequest(invoiceId)
  if not g_currentMission:getIsServer() then return end

  local invoice = self.invoices[invoiceId]

  invoice.status = Invoice.STATUS.SENT

  self:syncInvoices()
end

function InvoiceManager:handlePayRequest(farmId, invoiceId)
  if not g_currentMission:getIsServer() then return end

  local invoice = self.invoices[invoiceId]

  if g_currentMission:getIsServer() then
    -- Remove money from receiver farm
    g_currentMission:addMoneyChange(
      -invoice.total,
      invoice.receiverFarmId,
      MoneyType.INVOICES,
      true
    )

    -- Pay the creator farm
    g_currentMission:addMoneyChange(
      invoice.total,
      invoice.creatorFarmId,
      MoneyType.INVOICES,
      true
    )
  end

  g_farmManager:getFarmById(invoice.receiverFarmId):changeBalance(-invoice.total, MoneyType.INVOICES)
  g_farmManager:getFarmById(invoice.creatorFarmId):changeBalance(invoice.total, MoneyType.INVOICES)

  -- Change status of contract to be completed
  invoice.status = Invoice.STATUS.PAID

  -- Update paid amount to with the total amount for now, since we don't have partial payments yet
  invoice.paid = invoice.total -- TODO: Add more logic when adding partial payments

  -- Update all clients
  self:syncInvoices()
end

function InvoiceManager:handleDeleteRequest(invoiceId)
  if not g_currentMission:getIsServer() then return end

  self.invoices[invoiceId] = nil

  self:syncInvoices()
end

-- Helper functions to save invoice lines to/from XML
function InvoiceManager:saveInvoiceLinesToXml(xmlFile, invKey, lines)
  lines = lines or {}
  local linesKey = invKey .. ".lines"

  setXMLInt(xmlFile, linesKey .. "#count", #lines)

  for i, line in ipairs(lines) do
    local lineKey = string.format("%s.line(%d)", linesKey, i - 1)
    setXMLString(xmlFile, lineKey .. "#title", line.title or "")
    setXMLFloat(xmlFile, lineKey .. "#amount", tonumber(line.amount) or 0)
  end
end

-- Helper function to load invoice lines from XML, returns an array of { title, amount }
function InvoiceManager:loadInvoiceLinesFromXml(xmlFile, invKey)
  local lines = {}
  local linesKey = invKey .. ".lines"

  if not hasXMLProperty(xmlFile, linesKey) then
    return lines
  end

  local count = getXMLInt(xmlFile, linesKey .. "#count") or 0
  for i = 0, count - 1 do
    local lineKey = string.format("%s.line(%d)", linesKey, i)
    local title = getXMLString(xmlFile, lineKey .. "#title") or ""
    local amount = getXMLFloat(xmlFile, lineKey .. "#amount") or 0
    table.insert(lines, { title = title, amount = amount })
  end

  return lines
end
