<?php
/**
 * Signed MakePay webhook controller.
 */

namespace IPS\makepay\modules\front\webhook;

use IPS\makepay\Storage\Storage;

if (!\defined('\IPS\SUITE_UNIQUE_KEY')) {
    exit;
}

class _notify extends \IPS\Dispatcher\Controller
{
    protected function manage(): void
    {
        $body = (string) file_get_contents('php://input');
        $signature = (string) ($_SERVER['HTTP_X_MAKEPAY_SIGNATURE'] ?? '');
        $secret = Storage::getSecret('webhook_secret') ?: '';

        if (!$this->verifySignature($body, $signature, $secret)) {
            \IPS\Output::i()->sendOutput(json_encode(['error' => 'invalid signature']), 401, 'application/json');
            return;
        }

        $payload = json_decode($body, true) ?: [];
        $payment = $this->paymentObject($payload);
        $metadata = is_array($payload['metadata'] ?? null) ? $payload['metadata'] : [];
        if (!$metadata && is_array($payment['metadata'] ?? null)) {
            $metadata = $payment['metadata'];
        }
        if (!$metadata && is_array($payload['paymentLink']['payload']['metadata'] ?? null)) {
            $metadata = $payload['paymentLink']['payload']['metadata'];
        }
        $invoiceId = (int) ($metadata['invoiceId'] ?? 0);
        $transactionId = (int) ($metadata['transactionId'] ?? 0);
        $status = strtolower((string) ($payload['status'] ?? $payment['status'] ?? $payload['payment_status'] ?? ''));

        if ($invoiceId && $transactionId) {
            $transaction = \IPS\nexus\Transaction::load($transactionId);
            $this->applyStatus($transaction, $status, $payload);
        }

        \IPS\Output::i()->sendOutput(json_encode(['ok' => true]), 200, 'application/json');
    }

    protected function doReturn(): void
    {
        \IPS\Output::i()->redirect(\IPS\Http\Url::internal('app=nexus&module=payments&controller=invoices', 'front'));
    }

    private function applyStatus(\IPS\nexus\Transaction $transaction, string $status, array $payload): void
    {
        $payment = $this->paymentObject($payload);
        if (in_array($status, ['paid', 'completed', 'confirmed', 'succeeded'], true)) {
            $transaction->approve();
        } elseif (in_array($status, ['failed', 'expired', 'cancelled', 'canceled'], true)) {
            $transaction->status = \IPS\nexus\Transaction::STATUS_REFUSED;
            $transaction->save();
        } elseif ($status === 'refunded') {
            $transaction->status = \IPS\nexus\Transaction::STATUS_REFUNDED;
            $transaction->save();
        }

        $message = sprintf(
            'MakePay status: %s. Payment UID: %s. URL: %s',
            $status ?: 'unknown',
            (string) ($payment['uid'] ?? $payment['payment_uid'] ?? $payload['uid'] ?? $payload['payment_uid'] ?? ''),
            (string) ($payment['publicUrl'] ?? $payment['url'] ?? $payload['paymentUrl'] ?? $payload['payment_url'] ?? ''),
        );
        $transaction->invoice->addMessage($message, true);
    }

    private function verifySignature(string $body, string $signature, string $secret): bool
    {
        if ($body === '' || $signature === '' || $secret === '') {
            return false;
        }

        $timestamp = '';
        $provided = [];
        foreach (explode(',', $signature) as $part) {
            [$key, $value] = array_pad(explode('=', trim($part), 2), 2, '');
            if ($key === 't') {
                $timestamp = $value;
            }
            if ($key === 'v1') {
                $provided[] = $value;
            }
        }

        if ($timestamp !== '' && $provided && abs(time() - (int)$timestamp) <= 300) {
            $expected = hash_hmac('sha256', $timestamp . '.' . $body, $secret);
            foreach ($provided as $candidate) {
                if (hash_equals($expected, $candidate)) {
                    return true;
                }
            }
        }

        $expected = hash_hmac('sha256', $body, $secret);
        $normalized = preg_replace('/^sha256=/i', '', $signature) ?: $signature;

        return hash_equals($expected, $normalized);
    }

    private function paymentObject(array $payload): array
    {
        $data = is_array($payload['data'] ?? null) ? $payload['data'] : [];
        foreach ([
            $payload['paymentLink'] ?? null,
            $payload['payment_link'] ?? null,
            $data['paymentLink'] ?? null,
            $data['payment_link'] ?? null,
            $payload['payment'] ?? null,
            $data['payment'] ?? null,
        ] as $candidate) {
            if (is_array($candidate)) {
                return $candidate;
            }
        }

        return $payload;
    }
}
