<?php

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use RuntimeException;

/**
 * Fires and polls the Azure Function App's "sales_recon_deep_match" job — a
 * CP-SAT joint solve over card batches the synchronous matcher (SalesReconMerchantService::
 * reconCard) couldn't resolve one-at-a-time. Mirrors IdentifyDocumentService's
 * fire pattern, but the Function App returns 200 (not 202) with a jobId per the
 * daily_account_match/status async-job convention (the solve can run well past
 * Azure nginx's 230s connection limit, hence the job-store + poll design).
 *
 * Never blocks waiting for the solve — trigger() returns as soon as the job is
 * queued, pollOnce() checks status a single time. The caller (frontend) owns the
 * repeat; Laravel is a stateless relay both ways.
 */
class SalesReconDeepMatchService
{
    /**
     * @param  array $batches [{id, salesDate, batchNo, count, amount, confidence}]
     * @param  array $units   [{docNo, dateTime, amount, batchId, confidence}]
     * @return string jobId
     * @throws RuntimeException on config error or non-2xx response
     */
    public function trigger(array $batches, array $units, int $maxSeconds = 90, bool $sequence = true): string
    {
        $url = config('services.function_app.deep_match_url');
        if (empty($url)) {
            throw new RuntimeException('FUNCTION_APP_DEEP_MATCH_URL is not configured.');
        }

        $headers = ['Content-Type' => 'application/json'];
        $fnKey   = config('services.function_app.key');
        if ($fnKey) {
            $headers['x-functions-key'] = $fnKey;
        }

        // $sequence: card uses the per-stream non-crossing constraint; DuitNow/QR
        // passes false (its true settlement order isn't in the batch key — see the
        // deep-match runner). Defaults true so the card path is unchanged.
        $response = Http::timeout(30)->withHeaders($headers)->asJson()->post($url, [
            'batches'    => $batches,
            'units'      => $units,
            'maxSeconds' => $maxSeconds,
            'sequence'   => $sequence,
        ]);

        if (!$response->successful()) {
            Log::error('Sales Recon deep match: Function App trigger failed', [
                'status' => $response->status(),
                'body'   => substr($response->body(), 0, 500),
            ]);
            throw new RuntimeException('Deep match trigger failed [' . $response->status() . ']');
        }

        $jobId = $response->json('jobId');
        if (!$jobId) {
            throw new RuntimeException('Deep match trigger did not return a jobId.');
        }

        return $jobId;
    }

    /**
     * Single status check — no loop. { status: "running" } or
     * { status: "done", solved, totalEligible, provenOptimal, elapsedSeconds,
     *   assignments, stillUnresolved }.
     *
     * @throws RuntimeException on config error or non-2xx response
     */
    public function pollOnce(string $jobId): array
    {
        $url = config('services.function_app.deep_match_status_url');
        if (empty($url)) {
            throw new RuntimeException('FUNCTION_APP_DEEP_MATCH_STATUS_URL is not configured.');
        }

        $headers = [];
        $fnKey   = config('services.function_app.key');
        if ($fnKey) {
            $headers['x-functions-key'] = $fnKey;
        }

        $response = Http::timeout(15)->withHeaders($headers)->get($url, ['jobId' => $jobId]);

        if (!$response->successful()) {
            Log::error('Sales Recon deep match: Function App status check failed', [
                'status' => $response->status(),
                'body'   => substr($response->body(), 0, 500),
            ]);
            throw new RuntimeException('Deep match status check failed [' . $response->status() . ']');
        }

        return $response->json();
    }
}
