<?php

namespace PortalPeople\virtualstock\services;

use PortalPeople\virtualstock\exceptions\ValidationException;
use yii\base\Component;
use Yii;

/**
 * Handles inbound VirtualStock webhook events.
 *
 * VirtualStock can optionally send push notifications to a registered endpoint
 * when order/stock events occur. This service:
 *  1. Verifies the HMAC-SHA256 signature using the configured secret.
 *  2. Validates and decodes the payload.
 *  3. Raises a Yii2 application event so the host application can react.
 *
 * Register the controller endpoint in the host application's URL rules and
 * configure a `webhookSecret` in params-local.php:
 *
 *   'plugins.virtualstock' => [
 *       ...
 *       'webhookSecret' => 'my-secret-key',
 *   ]
 *
 * The incoming request must have an `X-Virtualstock-Signature` header containing:
 *   sha256=<hex_hmac>
 */
class WebhookService extends Component
{
    /** Shared secret for HMAC-SHA256 signature verification. */
    public string $webhookSecret = '';

    /** Name of the signature header. */
    public string $signatureHeader = 'X-Virtualstock-Signature';

    /** Supported event types. */
    public const EVENT_ORDER_UPDATED   = 'virtualstock.order.updated';
    public const EVENT_ORDER_CANCELLED = 'virtualstock.order.cancelled';
    public const EVENT_STOCK_UPDATED   = 'virtualstock.stock.updated';

    /**
     * Verify the request signature.
     *
     * @throws ValidationException if the signature is missing or invalid
     */
    public function verifySignature(string $rawBody, string $signatureHeader): void
    {
        if ($this->webhookSecret === '') {
            // No secret configured — signature verification is disabled.
            // Any caller can push events. Set webhookSecret in params-local.php
            // to enable HMAC-SHA256 verification.
            Yii::warning(
                '[WEBHOOK] Signature verification is disabled — webhookSecret is not configured.',
                'virtualstock'
            );
            return;
        }

        if ($signatureHeader === '') {
            throw new ValidationException('Webhook signature header is missing');
        }

        // Header format: "sha256=<hex>"
        if (!str_starts_with($signatureHeader, 'sha256=')) {
            throw new ValidationException('Webhook signature header format is invalid');
        }

        $providedHex  = substr($signatureHeader, 7);
        $expectedHex  = hash_hmac('sha256', $rawBody, $this->webhookSecret);

        if (!hash_equals($expectedHex, $providedHex)) {
            throw new ValidationException('Webhook signature verification failed');
        }
    }

    /**
     * Parse and validate a webhook payload.
     *
     * @param string $rawBody  Raw JSON request body
     * @return array<string, mixed>  Decoded payload
     * @throws ValidationException if the body is not valid JSON or missing required fields
     */
    public function parsePayload(string $rawBody): array
    {
        $data = json_decode($rawBody, true);

        if (!is_array($data)) {
            throw new ValidationException('Webhook payload is not valid JSON');
        }

        if (!isset($data['event'])) {
            throw new ValidationException('Webhook payload missing required field: event', ['event' => ['Required']]);
        }

        return $data;
    }

    /**
     * Handle a verified and parsed webhook payload.
     * Triggers a Yii2 application event so the host can respond.
     *
     * @param array<string, mixed> $payload
     */
    public function handle(array $payload): void
    {
        $event     = $payload['event'] ?? '';
        $orderData = $payload['order'] ?? $payload['data'] ?? [];

        Yii::info(
            sprintf('[WEBHOOK] Received event: %s', $event),
            'virtualstock'
        );

        Yii::$app->trigger('virtualstock.' . $event, new \yii\base\Event([
            'data' => $payload,
        ]));
    }

    /**
     * Full pipeline: verify signature → parse payload → handle event.
     *
     * @throws ValidationException on signature failure or invalid payload
     */
    public function process(string $rawBody, string $signatureHeaderValue): void
    {
        $this->verifySignature($rawBody, $signatureHeaderValue);
        $payload = $this->parsePayload($rawBody);
        $this->handle($payload);
    }
}
