<?php

namespace App\Application\Services;

use App\Models\IdempotencyRecord;
use App\Models\Merchant;
use App\Support\ApiResponse;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Symfony\Component\HttpFoundation\Response;

final class IdempotencyService
{
    public function execute(
        Request $request,
        Merchant $merchant,
        string $environment,
        string $operation,
        Closure $callback,
    ): Response {
        $key = trim((string) $request->header('Idempotency-Key', ''));
        if ($key === '') {
            return ApiResponse::error('IDEMPOTENCY_KEY_REQUIRED', 'Idempotency-Key header is required.', 422);
        }
        if (strlen($key) > 255) {
            return ApiResponse::error('IDEMPOTENCY_KEY_INVALID', 'Idempotency-Key must not exceed 255 characters.', 422);
        }

        $requestHash = $this->requestHash($request);

        return DB::transaction(function () use ($merchant, $environment, $operation, $key, $requestHash, $callback) {
            $record = IdempotencyRecord::query()
                ->where('merchant_id', $merchant->id)
                ->where('environment', $environment)
                ->where('operation', $operation)
                ->where('idempotency_key', $key)
                ->lockForUpdate()
                ->first();

            if ($record) {
                if (! hash_equals($record->request_hash, $requestHash)) {
                    return ApiResponse::error(
                        'IDEMPOTENCY_CONFLICT',
                        'The Idempotency-Key was already used with a different request.',
                        409,
                    );
                }

                if ($record->completed_at !== null) {
                    return response()->json($record->response_body, $record->response_code)
                        ->header('Idempotency-Replayed', 'true');
                }

                return ApiResponse::error('IDEMPOTENCY_IN_PROGRESS', 'A request with this Idempotency-Key is still being processed.', 409);
            }

            $record = IdempotencyRecord::create([
                'merchant_id' => $merchant->id,
                'environment' => $environment,
                'operation' => $operation,
                'idempotency_key' => $key,
                'request_hash' => $requestHash,
            ]);

            /** @var Response $response */
            $response = $callback($key);
            $body = $this->responseBody($response);

            $record->update([
                'response_code' => $response->getStatusCode(),
                'response_body' => $body,
                'completed_at' => now(),
            ]);

            return $response;
        }, 5);
    }

    private function requestHash(Request $request): string
    {
        $query = $request->query();
        ksort($query, SORT_STRING);

        return hash('sha256', implode("\n", [
            strtoupper($request->method()),
            $request->getPathInfo(),
            http_build_query($query, '', '&', PHP_QUERY_RFC3986),
            hash('sha256', $request->getContent()),
        ]));
    }

    private function responseBody(Response $response): array
    {
        if ($response instanceof JsonResponse) {
            return $response->getData(true);
        }

        $decoded = json_decode((string) $response->getContent(), true);
        return is_array($decoded) ? $decoded : ['data' => (string) $response->getContent()];
    }
}
