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

<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="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="generateToken" access="public" returntype="string" output="false">
  <cfreturn lcase(replace(createUUID(), "-", "", "all"))>
</cffunction>

<cfset data = readJsonBody()>
<cfset TaskID = val( structKeyExists(data,"TaskID") ? data.TaskID : 0 )>
<!--- Get UserID from request (auth header) or from JSON body as fallback --->
<cfset UserID = val( structKeyExists(request,"UserID") ? request.UserID : (structKeyExists(data,"UserID") ? data.UserID : 0) )>

<!--- Optional: Worker rating of customer (when required or voluntary) --->
<cfset workerRating = structKeyExists(data,"workerRating") ? data.workerRating : {}>

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

<cftry>
  <!--- Verify task exists --->
  <cfset qTask = queryExecute("
    SELECT t.ID, t.ClaimedByUserID, t.CompletedOn, t.OrderID, t.TaskTypeID, t.BusinessID,
           o.UserID, o.ServicePointID
    FROM Tasks t
    LEFT JOIN Orders o ON o.ID = t.OrderID
    WHERE t.ID = ?
  ", [ { value = TaskID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>

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

  <!--- Chat tasks (TaskTypeID = 2) can be closed without being claimed first --->
  <cfset isChatTask = (qTask.TaskTypeID EQ 2)>

  <cfif (NOT isChatTask) AND (qTask.ClaimedByUserID EQ 0)>
    <cfset apiAbort({ "OK": false, "ERROR": "not_claimed", "MESSAGE": "Task has not been claimed yet." })>
  </cfif>

  <cfif (NOT isChatTask) AND (UserID GT 0) AND (qTask.ClaimedByUserID NEQ UserID)>
    <cfset apiAbort({ "OK": false, "ERROR": "not_yours", "MESSAGE": "This task was claimed by someone else." })>
  </cfif>

  <!--- Check if already completed - use len() to handle both NULL and empty string --->
  <cfif len(trim(qTask.CompletedOn)) GT 0>
    <cfset apiAbort({ "OK": false, "ERROR": "already_completed", "MESSAGE": "Task has already been completed." })>
  </cfif>

  <!--- Check if this is a service point task (customer-facing) --->
  <cfset hasServicePoint = (val(qTask.ServicePointID) GT 0)>
  <cfset customerUserID = val(qTask.UserID)>
  <cfset workerUserID = val(qTask.ClaimedByUserID)>
  <cfset ratingRequired = false>
  <cfset ratingsCreated = []>

  <!--- For service point tasks, check if worker rating of customer is required (10% based on TaskID) --->
  <!--- Use TaskID modulo for deterministic selection - same task always has same requirement --->
  <cfif hasServicePoint AND customerUserID GT 0 AND workerUserID GT 0>
    <cfset ratingRequired = (TaskID MOD 10 EQ 0)>

    <!--- If rating required but not provided, return error --->
    <cfif ratingRequired AND structIsEmpty(workerRating)>
      <cfset apiAbort({
        "OK": false,
        "ERROR": "rating_required",
        "MESSAGE": "Please rate the customer before completing this task.",
        "CustomerUserID": customerUserID,
        "Questions": {
          "prepared": "Was the customer prepared?",
          "completedScope": "Was the scope clear?",
          "respectful": "Was the customer respectful?",
          "wouldAutoAssign": "Would you serve this customer again?"
        }
      })>
    </cfif>
  </cfif>

  <!--- Mark task as completed --->
  <cfset queryExecute("
    UPDATE Tasks
    SET CompletedOn = NOW()
    WHERE ID = ?
  ", [ { value = TaskID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>

  <!--- If task has an associated order, mark it as Delivered/Complete (status 5) --->
  <cfset orderUpdated = false>
  <cfif qTask.OrderID GT 0>
    <cfset queryExecute("
      UPDATE Orders
      SET StatusID = 5,
          LastEditedOn = NOW()
      WHERE ID = ?
    ", [ { value = qTask.OrderID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
    <cfset orderUpdated = true>
  </cfif>

  <!--- === PAYOUT LEDGER + ACTIVATION WITHHOLDING === --->
  <cfset ledgerCreated = false>
  <cfset workerUserID_for_payout = val(qTask.ClaimedByUserID)>
  <cfif workerUserID_for_payout GT 0>
    <!--- Get PayCents from the task --->
    <cfset qTaskPay = queryExecute("
      SELECT PayCents FROM Tasks WHERE ID = ?
    ", [ { value = TaskID, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>
    <cfset grossCents = val(qTaskPay.PayCents)>

    <cfif grossCents GT 0>
      <!--- Get worker's activation state --->
      <cfset qActivation = queryExecute("
        SELECT ActivationBalanceCents, ActivationCapCents
        FROM Users WHERE ID = ?
      ", [ { value = workerUserID_for_payout, cfsqltype = "cf_sql_integer" } ], { datasource = "payfrit" })>

      <cfset activationBalance = val(qActivation.ActivationBalanceCents)>
      <cfset activationCap = val(qActivation.ActivationCapCents)>
      <cfset remainingActivation = activationCap - activationBalance>

      <!--- Withholding: min($1, remaining, gross). Never negative. --->
      <cfset activationWithhold = 0>
      <cfif remainingActivation GT 0>
        <cfset activationWithhold = min(100, remainingActivation, grossCents)>
      </cfif>
      <cfset netCents = grossCents - activationWithhold>

      <!--- Insert ledger row --->
      <cfset queryExecute("
        INSERT INTO WorkPayoutLedgers
          (UserID, TaskID, GrossEarningsCents, ActivationWithheldCents, NetTransferCents, Status)
        VALUES
          (:userID, :taskID, :gross, :withheld, :net, 'pending_charge')
      ", {
        userID: workerUserID_for_payout,
        taskID: TaskID,
        gross: grossCents,
        withheld: activationWithhold,
        net: netCents
      }, { datasource = "payfrit" })>

      <!--- Increment activation balance --->
      <cfif activationWithhold GT 0>
        <cfset queryExecute("
          UPDATE Users
          SET ActivationBalanceCents = ActivationBalanceCents + :withhold
          WHERE ID = :userID
        ", {
          withhold: activationWithhold,
          userID: workerUserID_for_payout
        }, { datasource = "payfrit" })>
      </cfif>

      <cfset ledgerCreated = true>
    </cfif>
  </cfif>

  <!--- Create rating records for service point tasks --->
  <cfif hasServicePoint AND customerUserID GT 0 AND workerUserID GT 0>
    <!--- 1. Customer rates Worker (always created, submitted via receipt link) --->
    <cfset customerToken = generateToken()>
    <cfset queryExecute("
      INSERT INTO TaskRatings (
        TaskID, ByUserID, ForUserID, Direction,
        AccessToken, ExpiresOn
      ) VALUES (
        ?, ?, ?, 'customer_rates_worker',
        ?, DATE_ADD(NOW(), INTERVAL 24 HOUR)
      )
    ", [
      { value = TaskID, cfsqltype = "cf_sql_integer" },
      { value = customerUserID, cfsqltype = "cf_sql_integer" },
      { value = workerUserID, cfsqltype = "cf_sql_integer" },
      { value = customerToken, cfsqltype = "cf_sql_varchar" }
    ], { datasource = "payfrit" })>
    <cfset arrayAppend(ratingsCreated, { "direction": "customer_rates_worker", "token": customerToken })>

    <!--- 2. Worker rates Customer (if provided or required) --->
    <cfif NOT structIsEmpty(workerRating)>
      <cfset workerToken = generateToken()>
      <cfset queryExecute("
        INSERT INTO TaskRatings (
          TaskID, ByUserID, ForUserID, Direction,
          Prepared, CompletedScope, Respectful, WouldAutoAssign,
          AccessToken, ExpiresOn, CompletedOn
        ) VALUES (
          ?, ?, ?, 'worker_rates_customer',
          ?, ?, ?, ?,
          ?, DATE_ADD(NOW(), INTERVAL 24 HOUR), NOW()
        )
      ", [
        { value = TaskID, cfsqltype = "cf_sql_integer" },
        { value = workerUserID, cfsqltype = "cf_sql_integer" },
        { value = customerUserID, cfsqltype = "cf_sql_integer" },
        { value = structKeyExists(workerRating,"prepared") ? (workerRating.prepared ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(workerRating,"prepared") },
        { value = structKeyExists(workerRating,"completedScope") ? (workerRating.completedScope ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(workerRating,"completedScope") },
        { value = structKeyExists(workerRating,"respectful") ? (workerRating.respectful ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(workerRating,"respectful") },
        { value = structKeyExists(workerRating,"wouldAutoAssign") ? (workerRating.wouldAutoAssign ? 1 : 0) : javaCast("null",""), cfsqltype = "cf_sql_tinyint", null = !structKeyExists(workerRating,"wouldAutoAssign") },
        { value = workerToken, cfsqltype = "cf_sql_varchar" }
      ], { datasource = "payfrit" })>
      <cfset arrayAppend(ratingsCreated, { "direction": "worker_rates_customer", "submitted": true })>
    </cfif>
  </cfif>

  <cfset apiAbort({
    "OK": true,
    "ERROR": "",
    "MESSAGE": "Task completed successfully.",
    "TaskID": TaskID,
    "OrderUpdated": orderUpdated,
    "RatingsCreated": ratingsCreated,
    "LedgerCreated": ledgerCreated
  })>

  <cfcatch>
    <cfset apiAbort({
      "OK": false,
      "ERROR": "server_error",
      "MESSAGE": "Error completing task",
      "DETAIL": cfcatch.message
    })>
  </cfcatch>
</cftry>
