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

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

<!---
ResolveServicePoint API
=======================
Resolves a beacon's (UUID, Major, Minor) to a ServicePoint.
Used by customer apps to identify which table/location they're at.

Request:
  POST /api/beacon-sharding/resolve_servicepoint.cfm
  { "UUID": "f7826da6-4fa2-4e98-8024-bc5b71e0893e", "Major": 42, "Minor": 7 }

  Or for batch resolution:
  { "Beacons": [
      { "UUID": "...", "Major": 42, "Minor": 7 },
      { "UUID": "...", "Major": 42, "Minor": 8 }
    ]
  }

Response (single):
  {
    "OK": true,
    "ServicePointID": 456,
    "ServicePointName": "Table 7",
    "ServicePointCode": "T7",
    "BusinessID": 123,
    "BusinessName": "Joe's Diner"
  }

Response (batch):
  {
    "OK": true,
    "Results": [
      { "UUID": "...", "Major": 42, "Minor": 7, "ServicePointID": 456, ... },
      { "UUID": "...", "Major": 42, "Minor": 8, "ServicePointID": null, "Error": "not_found" }
    ]
  }
--->

<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;
}

function normStr(v) {
  if (isNull(v)) return "";
  return trim(toString(v));
}

function resolveSingleServicePoint(uuid, major, minor) {
  // First find the business by UUID + Major
  var qBiz = queryExecute(
    "SELECT b.ID AS BusinessID, b.Name AS BusinessName
     FROM Businesses b
     JOIN BeaconShards bs ON b.BeaconShardID = bs.ID
     WHERE bs.UUID = ? AND b.BeaconMajor = ?
     LIMIT 1",
    [
      { value=uuid, cfsqltype="cf_sql_varchar" },
      { value=major, cfsqltype="cf_sql_smallint" }
    ],
    { datasource="payfrit" }
  );

  if (qBiz.recordCount EQ 0) {
    return { Found=false, Error="business_not_found" };
  }

  // Then find the service point by BusinessID + Minor
  var qSP = queryExecute(
    "SELECT ID, Name, Code, TypeID, Description
     FROM ServicePoints
     WHERE BusinessID = ? AND BeaconMinor = ? AND IsActive = 1
     LIMIT 1",
    [
      { value=qBiz.BusinessID, cfsqltype="cf_sql_integer" },
      { value=minor, cfsqltype="cf_sql_smallint" }
    ],
    { datasource="payfrit" }
  );

  if (qSP.recordCount EQ 0) {
    return {
      Found = false,
      Error = "servicepoint_not_found",
      BusinessID = qBiz.BusinessID,
      BusinessName = qBiz.BusinessName
    };
  }

  return {
    Found = true,
    ServicePointID = qSP.ID,
    ServicePointName = qSP.Name,
    ServicePointCode = qSP.Code,
    ServicePointTypeID = qSP.TypeID,
    ServicePointDescription = qSP.Description,
    BusinessID = qBiz.BusinessID,
    BusinessName = qBiz.BusinessName
  };
}

data = readJsonBody();

// Check for batch request
if (structKeyExists(data, "Beacons") && isArray(data.Beacons)) {
  results = [];
  for (beacon in data.Beacons) {
    uuid = normStr(structKeyExists(beacon, "UUID") ? beacon.UUID : "");
    major = structKeyExists(beacon, "Major") && isNumeric(beacon.Major) ? int(beacon.Major) : 0;
    minor = structKeyExists(beacon, "Minor") && isNumeric(beacon.Minor) ? int(beacon.Minor) : -1;

    if (len(uuid) EQ 0 || major LTE 0 || minor LT 0) {
      arrayAppend(results, { UUID=uuid, Major=major, Minor=minor, ServicePointID=javaCast("null",""), Error="invalid_params" });
      continue;
    }

    resolved = resolveSingleServicePoint(uuid, major, minor);
    if (resolved.Found) {
      arrayAppend(results, {
        UUID = uuid,
        Major = major,
        Minor = minor,
        ServicePointID = resolved.ServicePointID,
        ServicePointName = resolved.ServicePointName,
        ServicePointCode = resolved.ServicePointCode,
        BusinessID = resolved.BusinessID,
        BusinessName = resolved.BusinessName
      });
    } else {
      var errResult = { UUID=uuid, Major=major, Minor=minor, ServicePointID=javaCast("null",""), Error=resolved.Error };
      if (structKeyExists(resolved, "BusinessID")) {
        errResult.BusinessID = resolved.BusinessID;
        errResult.BusinessName = resolved.BusinessName;
      }
      arrayAppend(results, errResult);
    }
  }

  writeOutput(serializeJSON({ OK=true, COUNT=arrayLen(results), Results=results }));
  abort;
}

// Single request
uuid = normStr(structKeyExists(data, "UUID") ? data.UUID : "");
major = 0;
minor = -1;
if (structKeyExists(data, "Major") && isNumeric(data.Major)) {
  major = int(data.Major);
}
if (structKeyExists(data, "Minor") && isNumeric(data.Minor)) {
  minor = int(data.Minor);
}

// Also check URL params
if (len(uuid) EQ 0 && structKeyExists(url, "UUID")) {
  uuid = normStr(url.UUID);
}
if (major LTE 0 && structKeyExists(url, "Major") && isNumeric(url.Major)) {
  major = int(url.Major);
}
if (minor LT 0 && structKeyExists(url, "Minor") && isNumeric(url.Minor)) {
  minor = int(url.Minor);
}

if (len(uuid) EQ 0) {
  apiAbort({ OK=false, ERROR="missing_uuid", MESSAGE="UUID is required" });
}
if (major LTE 0) {
  apiAbort({ OK=false, ERROR="missing_major", MESSAGE="Major is required" });
}
if (minor LT 0) {
  apiAbort({ OK=false, ERROR="missing_minor", MESSAGE="Minor is required" });
}

resolved = resolveSingleServicePoint(uuid, major, minor);

if (!resolved.Found) {
  errResponse = { OK=false, ERROR=resolved.Error };
  if (structKeyExists(resolved, "BusinessID")) {
    errResponse.BusinessID = resolved.BusinessID;
    errResponse.BusinessName = resolved.BusinessName;
    errResponse.MESSAGE = "Service point not found for this business";
  } else {
    errResponse.MESSAGE = "No business found for this beacon";
  }
  apiAbort(errResponse);
}
</cfscript>

<cfoutput>#serializeJSON({
  OK = true,
  ServicePointID = resolved.ServicePointID,
  ServicePointName = resolved.ServicePointName,
  ServicePointCode = resolved.ServicePointCode,
  ServicePointTypeID = resolved.ServicePointTypeID,
  ServicePointDescription = resolved.ServicePointDescription,
  BusinessID = resolved.BusinessID,
  BusinessName = resolved.BusinessName
})#</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>
