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

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

<!---
VerifyBeaconBroadcast API
=========================
Records a scan confirmation that a beacon is broadcasting the expected values.
Called by provisioning app after writing config and seeing the beacon advertise.

Request:
  POST /api/beacon-sharding/verify_beacon_broadcast.cfm
  {
    "HardwareId": "AA:BB:CC:DD:EE:FF",
    "UUID": "f7826da6-4fa2-4e98-8024-bc5b71e0893e",
    "Major": 42,
    "Minor": 7,
    "RSSI": -65,
    "SeenAt": "2024-01-15T10:30:00Z"
  }

Response:
  {
    "OK": true,
    "BeaconHardwareID": 1,
    "Status": "verified",
    "VerifiedAt": "2024-01-15T10:30:00Z"
  }

Logic:
  1. Find beacon hardware by HardwareId
  2. Verify UUID/Major/Minor match expected values
  3. Update status to "verified" and record timestamp/RSSI
--->

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

data = readJsonBody();

// Required fields
hardwareId = normStr(structKeyExists(data, "HardwareId") ? data.HardwareId : "");
if (len(hardwareId) EQ 0) {
  apiAbort({ OK=false, ERROR="missing_hardware_id", MESSAGE="HardwareId is required" });
}

beaconUUID = normStr(structKeyExists(data, "UUID") ? data.UUID : "");
if (len(beaconUUID) EQ 0) {
  apiAbort({ OK=false, ERROR="missing_uuid", MESSAGE="UUID is required" });
}

major = 0;
if (structKeyExists(data, "Major") && isNumeric(data.Major)) {
  major = int(data.Major);
}

minor = 0;
if (structKeyExists(data, "Minor") && isNumeric(data.Minor)) {
  minor = int(data.Minor);
}

// Optional fields
rssi = "";
if (structKeyExists(data, "RSSI") && isNumeric(data.RSSI)) {
  rssi = int(data.RSSI);
}

seenAt = now();
if (structKeyExists(data, "SeenAt") && len(data.SeenAt)) {
  try {
    seenAt = parseDateTime(data.SeenAt);
  } catch(any e) {
    // Use current time if parse fails
  }
}
</cfscript>

<!--- Find beacon hardware --->
<cfquery name="qHW" datasource="payfrit">
  SELECT ID, BusinessID, ServicePointID, ShardUUID, Major, Minor, Status
  FROM BeaconHardware
  WHERE HardwareId = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">
  LIMIT 1
</cfquery>

<cfif qHW.recordCount EQ 0>
  <cfset apiAbort({ OK=false, ERROR="hardware_not_found", MESSAGE="Beacon hardware not registered" })>
</cfif>

<!--- Verify broadcast matches expected values --->
<cfif compareNoCase(qHW.ShardUUID, beaconUUID) NEQ 0>
  <cfset apiAbort({
    OK=false,
    ERROR="uuid_mismatch",
    MESSAGE="Beacon is broadcasting wrong UUID",
    ExpectedUUID=qHW.ShardUUID,
    BroadcastUUID=beaconUUID
  })>
</cfif>

<cfif qHW.Major NEQ major>
  <cfset apiAbort({
    OK=false,
    ERROR="major_mismatch",
    MESSAGE="Beacon is broadcasting wrong Major",
    ExpectedMajor=qHW.Major,
    BroadcastMajor=major
  })>
</cfif>

<cfif qHW.Minor NEQ minor>
  <cfset apiAbort({
    OK=false,
    ERROR="minor_mismatch",
    MESSAGE="Beacon is broadcasting wrong Minor",
    ExpectedMinor=qHW.Minor,
    BroadcastMinor=minor
  })>
</cfif>

<!--- Update to verified status --->
<cfquery datasource="payfrit">
  UPDATE BeaconHardware
  SET
    Status = 'verified',
    VerifiedAt = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#seenAt#">,
    LastSeenAt = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#seenAt#">,
    <cfif len(rssi)>LastRssi = <cfqueryparam cfsqltype="cf_sql_smallint" value="#rssi#">,</cfif>
    UpdatedAt = NOW()
  WHERE HardwareId = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">
</cfquery>

<cfoutput>#serializeJSON({
  OK = true,
  BeaconHardwareID = qHW.ID,
  HardwareId = hardwareId,
  BusinessID = qHW.BusinessID,
  ServicePointID = qHW.ServicePointID,
  Status = "verified",
  VerifiedAt = dateTimeFormat(seenAt, "yyyy-mm-dd'T'HH:nn:ss'Z'")
})#</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>
