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

<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">

<!---
AllocateServicePointMinor API
=============================
Allocates or retrieves the beacon Minor value for a service point.

Request:
  POST /api/beacon-sharding/allocate_servicepoint_minor.cfm
  { "BusinessID": 123, "ServicePointID": 456 }

Response:
  {
    "OK": true,
    "ServicePointID": 456,
    "BusinessID": 123,
    "BeaconMinor": 7
  }

Logic:
  1. Verify service point belongs to the business
  2. If already has Minor assigned, return it
  3. Otherwise, find next available Minor within the business
  4. Minor is stable - does NOT change when hardware is replaced
--->

<cftry>
<cfscript>
function apiAbort(obj) {
  writeOutput(serializeJSON(obj));
  abort;
}

function readJsonBody() {
  raw = toString(getHttpRequestData().content);
  if (isNull(raw) || len(trim(raw)) EQ 0) return {};
  try {
    parsed = deserializeJSON(raw);
  } catch(any e) {
    apiAbort({ OK=false, ERROR="bad_json", MESSAGE="Invalid JSON body" });
  }
  if (!isStruct(parsed)) return {};
  return parsed;
}

data = readJsonBody();
httpHeaders = getHttpRequestData().headers;

// Get BusinessID from: session > body > X-Business-ID header > URL
bizId = 0;
if (structKeyExists(request, "BusinessID") && isNumeric(request.BusinessID) && request.BusinessID GT 0) {
  bizId = int(request.BusinessID);
}
if (bizId LTE 0 && structKeyExists(data, "BusinessID") && isNumeric(data.BusinessID) && data.BusinessID GT 0) {
  bizId = int(data.BusinessID);
}
if (bizId LTE 0 && structKeyExists(httpHeaders, "X-Business-ID") && isNumeric(httpHeaders["X-Business-ID"]) && httpHeaders["X-Business-ID"] GT 0) {
  bizId = int(httpHeaders["X-Business-ID"]);
}
if (bizId LTE 0 && structKeyExists(url, "BusinessID") && isNumeric(url.BusinessID) && url.BusinessID GT 0) {
  bizId = int(url.BusinessID);
}
if (bizId LTE 0) {
  apiAbort({ OK=false, ERROR="missing_business_id", MESSAGE="BusinessID is required" });
}

// Get ServicePointID
spId = 0;
if (structKeyExists(data, "ServicePointID") && isNumeric(data.ServicePointID) && data.ServicePointID GT 0) {
  spId = int(data.ServicePointID);
}
if (spId LTE 0 && structKeyExists(url, "ServicePointID") && isNumeric(url.ServicePointID) && url.ServicePointID GT 0) {
  spId = int(url.ServicePointID);
}
if (spId LTE 0) {
  apiAbort({ OK=false, ERROR="missing_servicepoint_id", MESSAGE="ServicePointID is required" });
}
</cfscript>

<!--- Verify service point exists and belongs to the business --->
<cfquery name="qSP" datasource="payfrit">
  SELECT ID, BusinessID, Name, BeaconMinor
  FROM ServicePoints
  WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">
  LIMIT 1
</cfquery>

<cfif qSP.recordCount EQ 0>
  <cfset apiAbort({ OK=false, ERROR="invalid_servicepoint", MESSAGE="Service point not found" })>
</cfif>

<cfif qSP.BusinessID NEQ bizId>
  <cfset apiAbort({ OK=false, ERROR="access_denied", MESSAGE="Service point does not belong to this business" })>
</cfif>

<!--- If already allocated, return existing Minor --->
<cfif NOT isNull(qSP.BeaconMinor)>
  <cfoutput>#serializeJSON({
    OK = true,
    ServicePointID = spId,
    BusinessID = bizId,
    BeaconMinor = qSP.BeaconMinor,
    ServicePointName = qSP.Name,
    AlreadyAllocated = true
  })#</cfoutput>
  <cfabort>
</cfif>

<!--- Find next available Minor within this business --->
<!--- Minor values: 0-65535 (uint16), we start from 1 to avoid 0 --->
<cfquery name="qMaxMinor" datasource="payfrit">
  SELECT COALESCE(MAX(BeaconMinor), 0) AS MaxMinor
  FROM ServicePoints
  WHERE BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
</cfquery>

<cfset nextMinor = qMaxMinor.MaxMinor + 1>

<!--- Sanity check --->
<cfif nextMinor GT 65535>
  <cfset apiAbort({ OK=false, ERROR="business_full", MESSAGE="Business has reached maximum service points (65535)" })>
</cfif>

<!--- Assign Minor to service point --->
<cfquery datasource="payfrit">
  UPDATE ServicePoints
  SET BeaconMinor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#nextMinor#">
  WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">
    AND BeaconMinor IS NULL
</cfquery>

<cfoutput>#serializeJSON({
  OK = true,
  ServicePointID = spId,
  BusinessID = bizId,
  BeaconMinor = nextMinor,
  ServicePointName = qSP.Name,
  AlreadyAllocated = false
})#</cfoutput>

<cfcatch type="any">
  <cfheader statuscode="200" statustext="OK">
  <cfcontent type="application/json; charset=utf-8" reset="true">
  <cfoutput>#serializeJSON({ OK=false, ERROR="server_error", MESSAGE=cfcatch.message, DETAIL=cfcatch.detail })#</cfoutput>
</cfcatch>
</cftry>
