<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">

<cffunction name="readJsonBody" access="public" returntype="struct" output="false">
  <cfset var raw = getHttpRequestData().content>
  <cfif isNull(raw) OR len(trim(raw)) EQ 0>
    <cfreturn {}>
  </cfif>
  <cftry>
    <cfset var data = deserializeJSON(raw)>
    <cfif isStruct(data)>
      <cfreturn data>
    <cfelse>
      <cfreturn {}>
    </cfif>
    <cfcatch>
      <cfreturn {}>
    </cfcatch>
  </cftry>
</cffunction>

<cffunction name="apiAbort" access="public" returntype="void" output="true">
  <cfargument name="payload" type="struct" required="true">
  <cfcontent type="application/json; charset=utf-8">
  <cfoutput>#serializeJSON(arguments.payload)#</cfoutput>
  <cfabort>
</cffunction>

<cffunction name="buildLineItemsGraph" access="public" returntype="struct" output="false">
  <cfargument name="OrderID" type="numeric" required="true">

  <cfset var out = {}>
  <cfset out.items = {}> <!--- lineItemId -> struct --->
  <cfset out.children = {}> <!--- parentLineItemId -> array(lineItemId) --->
  <cfset out.itemMeta = {}> <!--- ItemID -> struct(meta) --->

  <cfset var qLI = queryTimed(
    "
      SELECT
        ID,
        ParentOrderLineItemID,
        ItemID,
        IsDeleted
      FROM OrderLineItems
      WHERE OrderID = ?
      ORDER BY ID
    ",
    [ { value = arguments.OrderID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

  <cfif qLI.recordCount EQ 0>
    <cfreturn out>
  </cfif>

  <cfset var itemIds = []>
  <cfloop query="qLI">
    <cfset out.items[qLI.ID] = {
      "id": qLI.ID,
      "parentId": qLI.ParentOrderLineItemID,
      "itemId": qLI.ItemID,
      "isDeleted": (qLI.IsDeleted EQ true)
    }>
    <cfif NOT structKeyExists(out.children, qLI.ParentOrderLineItemID)>
      <cfset out.children[qLI.ParentOrderLineItemID] = []>
    </cfif>
    <cfset arrayAppend(out.children[qLI.ParentOrderLineItemID], qLI.ID)>
    <cfset arrayAppend(itemIds, qLI.ItemID)>
  </cfloop>

  <!--- Load meta for involved items --->
  <cfset var uniq = {} >
  <cfloop array="#itemIds#" index="iid">
    <cfset uniq[iid] = true>
  </cfloop>
  <cfset var uniqIds = structKeyArray(uniq)>
  <cfif arrayLen(uniqIds) GT 0>
    <cfset var inList = arrayToList(uniqIds)>
    <cfset var qMeta = queryTimed(
      "
        SELECT
          ID,
          RequiresChildSelection,
          MaxNumSelectionReq
        FROM Items
        WHERE ID IN (#inList#)
      ",
      [],
      { datasource = "payfrit" }
    )>

    <cfloop query="qMeta">
      <cfset out.itemMeta[qMeta.ID] = {
        "requires": qMeta.RequiresChildSelection,
        "maxSel": qMeta.MaxNumSelectionReq
      }>
    </cfloop>
  </cfif>

  <cfreturn out>
</cffunction>

<cffunction name="hasSelectedDescendant" access="public" returntype="boolean" output="false">
  <cfargument name="graph" type="struct" required="true">
  <cfargument name="lineItemId" type="numeric" required="true">

  <cfset var stack = []>
  <cfif structKeyExists(arguments.graph.children, arguments.lineItemId)>
    <cfset stack = duplicate(arguments.graph.children[arguments.lineItemId])>
  </cfif>

  <cfloop condition="arrayLen(stack) GT 0">
    <cfset var id = stack[arrayLen(stack)]>
    <cfset arrayDeleteAt(stack, arrayLen(stack))>

    <cfif structKeyExists(arguments.graph.items, id)>
      <cfset var node = arguments.graph.items[id]>
      <cfif NOT node.isDeleted>
        <cfreturn true>
      </cfif>
      <cfif structKeyExists(arguments.graph.children, id)>
        <cfset var kids = arguments.graph.children[id]>
        <cfloop array="#kids#" index="kidId">
          <cfset arrayAppend(stack, kidId)>
        </cfloop>
      </cfif>
    </cfif>
  </cfloop>

  <cfreturn false>
</cffunction>

<cfinclude template="../grants/_grantUtils.cfm">
<cfset data = readJsonBody()>
<cfset OrderID = val( structKeyExists(data,"OrderID") ? data.OrderID : 0 )>

<cfif OrderID LTE 0>
  <cfset apiAbort({ "OK": false, "ERROR": "missing_orderid", "MESSAGE": "OrderID is required.", "DETAIL": "" })>
</cfif>

<cftry>
  <cfset qOrder = queryTimed(
    "
      SELECT ID, StatusID, OrderTypeID, BusinessID, ServicePointID,
             GrantID, GrantOwnerBusinessID
      FROM Orders
      WHERE ID = ?
      LIMIT 1
    ",
    [ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

  <cfif qOrder.recordCount EQ 0>
    <cfset apiAbort({ "OK": false, "ERROR": "not_found", "MESSAGE": "Order not found.", "DETAIL": "" })>
  </cfif>

  <cfif qOrder.StatusID NEQ 0>
    <cfset apiAbort({ "OK": false, "ERROR": "bad_state", "MESSAGE": "Order is not in cart state.", "DETAIL": "" })>
  </cfif>

  <!--- OrderTypeID: 1=dine-in, 2=takeaway, 3=delivery (0=undecided is invalid for submit) --->
  <cfif qOrder.OrderTypeID LT 1 OR qOrder.OrderTypeID GT 3>
    <cfset apiAbort({ "OK": false, "ERROR": "bad_type", "MESSAGE": "Order type must be set before submitting (1=dine-in, 2=takeaway, 3=delivery).", "DETAIL": "" })>
  </cfif>

  <!--- Delivery orders require an address --->
  <cfif qOrder.OrderTypeID EQ 3>
    <cfset qAddr = queryTimed(
      "SELECT AddressID FROM Orders WHERE ID = ? LIMIT 1",
      [ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
      { datasource = "payfrit" }
    )>
    <cfif qAddr.AddressID LTE 0 OR isNull(qAddr.AddressID)>
      <cfset apiAbort({ "OK": false, "ERROR": "missing_address", "MESSAGE": "Delivery orders require a delivery address.", "DETAIL": "" })>
    </cfif>
  </cfif>

  <!--- SP-SM: Re-validate grant is still active (instant revocation enforcement) --->
  <cfif val(qOrder.GrantID) GT 0>
    <cfset qGrantCheck = queryTimed(
      "SELECT StatusID, TimePolicyType, TimePolicyData
       FROM ServicePointGrants WHERE ID = ? LIMIT 1",
      [ { value = qOrder.GrantID, cfsqltype = "cf_sql_integer" } ],
      { datasource = "payfrit" }
    )>
    <cfif qGrantCheck.recordCount EQ 0 OR qGrantCheck.StatusID NEQ 1>
      <cfset apiAbort({ "OK": false, "ERROR": "grant_revoked", "MESSAGE": "Access to this service point has been revoked.", "DETAIL": "" })>
    </cfif>
    <cfif NOT isGrantTimeActive(qGrantCheck.TimePolicyType, qGrantCheck.TimePolicyData)>
      <cfset apiAbort({ "OK": false, "ERROR": "grant_time_expired", "MESSAGE": "Service point access is no longer available at this time.", "DETAIL": "" })>
    </cfif>
  </cfif>

  <!--- Must have at least one non-deleted root line item --->
  <cfset qRoots = queryTimed(
    "
      SELECT COUNT(*) AS Cnt
      FROM OrderLineItems
      WHERE OrderID = ?
        AND ParentOrderLineItemID = 0
        AND IsDeleted = 0
    ",
    [ { value = OrderID, cfsqltype = "cf_sql_integer" } ],
    { datasource = "payfrit" }
  )>

  <cfif qRoots.Cnt LTE 0>
    <cfset apiAbort({ "OK": false, "ERROR": "empty_order", "MESSAGE": "Order has no items.", "DETAIL": "" })>
  </cfif>

  <!--- Validate requires-descendant and max immediate selections --->
  <cfset graph = buildLineItemsGraph(OrderID)>

  <!--- Loop all non-deleted nodes and validate --->
  <cfloop collection="#graph.items#" item="k">
    <cfset node = graph.items[k]>
    <cfif node.isDeleted>
      <!--- skip deleted nodes --->
      <cfcontinue>
    </cfif>

    <cfset meta = structKeyExists(graph.itemMeta, node.itemId) ? graph.itemMeta[node.itemId] : { "requires": 0, "maxSel": 0 }>

    <!--- max immediate selections --->
    <cfset maxSel = val(meta.maxSel)>
    <cfif maxSel GT 0>
      <cfset selCount = 0>
      <cfif structKeyExists(graph.children, node.id)>
        <cfset kids = graph.children[node.id]>
        <cfloop array="#kids#" index="kidId">
          <cfif structKeyExists(graph.items, kidId)>
            <cfset kidNode = graph.items[kidId]>
            <cfif NOT kidNode.isDeleted>
              <cfset selCount = selCount + 1>
            </cfif>
          </cfif>
        </cfloop>
      </cfif>

      <cfif selCount GT maxSel>
        <cfset apiAbort({
          "OK": false,
          "ERROR": "max_selection_exceeded",
          "MESSAGE": "Too many selections under a modifier group.",
          "DETAIL": "LineItemID #node.id# has #selCount# immediate children selected; max is #maxSel#."
        })>
      </cfif>
    </cfif>

    <!--- requires descendant selection --->
    <cfif val(meta.requires) EQ 1>
      <cfif NOT hasSelectedDescendant(graph, node.id)>
        <cfset apiAbort({
          "OK": false,
          "ERROR": "required_selection_missing",
          "MESSAGE": "A required modifier selection is missing.",
          "DETAIL": "LineItemID #node.id# requires at least one descendant selection."
        })>
      </cfif>
    </cfif>
  </cfloop>

  <!--- Submit: mark submitted + status 1 --->
  <cfset queryTimed(
    "
      UPDATE Orders
      SET
        StatusID = 1,
        SubmittedOn = ?,
        LastEditedOn = ?
      WHERE ID = ?
    ",
    [
      { value = now(), cfsqltype = "cf_sql_timestamp" },
      { value = now(), cfsqltype = "cf_sql_timestamp" },
      { value = OrderID, cfsqltype = "cf_sql_integer" }
    ],
    { datasource = "payfrit" }
  )>

  <cfset apiAbort({ "OK": true, "ERROR": "", "OrderID": OrderID, "MESSAGE": "submitted" })>

  <cfcatch>
    <cfset apiAbort({
      "OK": false,
      "ERROR": "server_error",
      "MESSAGE": "DB error submitting order",
      "DETAIL": cfcatch.message
    })>
  </cfcatch>
</cftry>
