<?php

namespace App\Http\Controllers\Api;

use App\Jobs\ProposeFixForIssue;
use App\Services\AuditLogger;
use App\Services\Noc\IncidentMapper;
use App\Services\Noc\NocClient;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Log;

/**
 * Inbound webhook from NOC. Fired by NOC's IncidentManagerService whenever an
 * incident is opened, updated, or resolved. We accept both the embedded
 * incident payload AND a fetch-by-id flow (for compactness on NOC side).
 *
 * Auth: HMAC-SHA256 of the raw request body using a shared secret stored in
 * config('hsap.noc.webhook_secret'). NOC sends the signature in
 * `X-NOC-Signature: sha256=<hex>`. Constant-time compare.
 *
 * Replay protection: NOC includes `X-NOC-Timestamp` (unix). Reject anything
 * older than 5 minutes — keeps stale captures from being replayed.
 */
class NocWebhookController extends Controller
{
    public function receive(Request $request, IncidentMapper $mapper, NocClient $noc)
    {
        $secret = (string) config('hsap.noc.webhook_secret');
        if ($secret === '') {
            Log::warning('hsap.noc: webhook received but secret not configured — rejecting');
            abort(503, 'NOC webhook not configured');
        }

        $signature = (string) $request->header('X-NOC-Signature', '');
        $timestamp = (int)    $request->header('X-NOC-Timestamp', 0);
        $body      = $request->getContent();

        // Replay window
        if (abs(time() - $timestamp) > 300) {
            Log::warning('hsap.noc: webhook timestamp outside 5-minute window', [
                'received_ts' => $timestamp, 'now' => time(),
            ]);
            abort(401, 'Stale webhook');
        }

        // HMAC verify — signature is over `${timestamp}.${body}` to prevent
        // a captured signature from being reused with a fresher timestamp.
        $expected = 'sha256=' . hash_hmac('sha256', "{$timestamp}.{$body}", $secret);
        if (! hash_equals($expected, $signature)) {
            Log::warning('hsap.noc: webhook HMAC mismatch', ['actor_ip' => $request->ip()]);
            abort(401, 'Invalid signature');
        }

        $payload = json_decode($body, true);
        if (! is_array($payload)) abort(422, 'Invalid JSON');

        // Two delivery shapes supported:
        //   A. { event: 'incident.opened', incident: {...full row...} }
        //   B. { event: 'incident.opened', incident_id: 1234 }   ← we fetch
        $event = $payload['event'] ?? null;
        $incidentRow = $payload['incident'] ?? null;

        if (! $incidentRow && isset($payload['incident_id'])) {
            $incidentRow = $noc->incident($payload['incident_id']);
        }

        if (! is_array($incidentRow)) abort(422, 'Missing incident payload');

        $issue = $mapper->upsertFromNoc($incidentRow);

        AuditLogger::log('system', 'noc-webhook', 'noc-webhook',
            'noc.webhook_received', $issue ? 'issue' : null, $issue ? (string) $issue->id : null,
            ['event' => $event, 'noc_public_id' => $incidentRow['public_id'] ?? null,
             'mapped' => (bool) $issue], $request);

        // Real-time AI proposal kick-off — only on incident.opened to avoid
        // re-proposing on every status update. Skip if we already have a
        // pending action for this issue (idempotency handled inside the job).
        if ($issue && in_array($event, ['incident.opened', 'incident.created'], true)
            && $issue->fixable && $issue->category !== 'other') {
            ProposeFixForIssue::dispatch($issue->id);
        }

        return response()->json([
            'ok' => true,
            'mapped_issue_id' => $issue?->id,
        ]);
    }
}
