<?php

namespace App\Http\Controllers\Webhook;

use App\Http\Controllers\Controller;
use App\Models\ReservedAccount;
use App\Models\WebhookEvent;
use App\Services\SystemSettingService;
use App\Services\UserActivityService;
use App\Services\WalletService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class PaymentPointWebhookController extends Controller
{
    public function __invoke(
        Request $request,
        SystemSettingService $systemSettingService,
        WalletService $walletService,
        UserActivityService $userActivityService
    ): JsonResponse {
        $payload = $request->all();
        $this->assertSignature($request, $systemSettingService);

        $notificationStatus = (string) (data_get($payload, 'notification_status') ?? 'unknown');
        $transactionId = (string) (data_get($payload, 'transaction_id') ?? '');
        $eventReference = $transactionId !== ''
            ? 'paymentpoint:'.$notificationStatus.':'.$transactionId
            : 'paymentpoint:'.$notificationStatus.':'.hash('sha256', $request->getContent());

        $event = WebhookEvent::firstOrCreate(
            ['event_reference' => $eventReference],
            [
                'provider' => 'paymentpoint',
                'payload_json' => $payload,
            ]
        );

        if ($event->processed_at !== null) {
            return response()->json(['ok' => true, 'duplicate' => true]);
        }

        $receiverAccountNumber = trim((string) (data_get($payload, 'receiver.account_number') ?? ''));
        $reservedAccount = $receiverAccountNumber !== ''
            ? $this->findReservedAccountByAccountNumber($receiverAccountNumber)
            : null;

        $matched = (bool) $reservedAccount;
        $credited = false;

        $settlementAmount = (float) (data_get($payload, 'settlement_amount') ?? 0);
        $amountPaid = (float) (data_get($payload, 'amount_paid') ?? 0);
        $creditAmount = round($settlementAmount > 0 ? $settlementAmount : $amountPaid, 2);

        if ($reservedAccount && $this->isSuccessfulPaymentEvent($payload) && $creditAmount > 0) {
            $referenceId = $transactionId !== ''
                ? $transactionId
                : substr(hash('sha256', json_encode($payload)), 0, 24);

            $walletService->credit(
                user: $reservedAccount->user,
                amount: $creditAmount,
                reference: "wallet:paymentpoint:{$referenceId}",
                narration: 'PaymentPoint reserved account funding',
                meta: [
                    'provider' => 'paymentpoint',
                    'reserved_account_id' => $reservedAccount->id,
                    'account_reference' => $reservedAccount->account_reference,
                    'receiver_account_number' => $receiverAccountNumber,
                    'amount_paid' => round($amountPaid, 2),
                    'settlement_amount' => round($settlementAmount, 2),
                    'payload' => $payload,
                ],
                source: 'paymentpoint'
            );

            $userActivityService->touch($reservedAccount->user);
            $credited = true;
        }

        $event->update(['processed_at' => now()]);

        return response()->json([
            'ok' => true,
            'provider' => 'paymentpoint',
            'matched' => $matched,
            'credited' => $credited,
        ]);
    }

    protected function assertSignature(Request $request, SystemSettingService $systemSettingService): void
    {
        $secret = (string) $systemSettingService->get(
            'webhooks.paymentpoint',
            'secret',
            config('vtu.webhooks.paymentpoint.secret', '')
        );

        if ($secret === '') {
            $secret = (string) $systemSettingService->get(
                'payments.paymentpoint',
                'secret_key',
                config('services.paymentpoint.secret_key', '')
            );
        }

        if ($secret === '') {
            if (app()->environment(['local', 'testing'])) {
                return;
            }

            abort(503, 'PaymentPoint webhook secret is not configured.');
        }

        $headerName = (string) $systemSettingService->get(
            'webhooks.paymentpoint',
            'signature_header',
            config('vtu.webhooks.paymentpoint.signature_header', 'Paymentpoint-Signature')
        );
        $provided = (string) $request->header($headerName, '');
        $expected = hash_hmac('sha256', $request->getContent(), $secret);

        abort_unless(
            $provided !== '' && hash_equals($expected, $provided),
            401,
            'Invalid PaymentPoint webhook signature.'
        );
    }

    protected function isSuccessfulPaymentEvent(array $payload): bool
    {
        $notificationStatus = strtolower((string) (data_get($payload, 'notification_status') ?? ''));
        $transactionStatus = strtolower((string) (data_get($payload, 'transaction_status') ?? ''));

        return in_array($transactionStatus, ['success', 'successful', 'completed'], true)
            || in_array($notificationStatus, ['payment_successful', 'success'], true);
    }

    protected function findReservedAccountByAccountNumber(string $accountNumber): ?ReservedAccount
    {
        $normalizedNumber = preg_replace('/\D+/', '', $accountNumber);
        if ($normalizedNumber === '') {
            return null;
        }

        $accounts = ReservedAccount::query()
            ->with('user')
            ->where('provider', 'paymentpoint')
            ->where('status', 'active')
            ->get();

        foreach ($accounts as $reservedAccount) {
            foreach ($this->extractAccountEntries($reservedAccount->accounts_json) as $entry) {
                $candidate = preg_replace('/\D+/', '', (string) (
                    data_get($entry, 'accountNumber')
                    ?? data_get($entry, 'account_number')
                    ?? ''
                ));

                if ($candidate !== '' && $candidate === $normalizedNumber) {
                    return $reservedAccount;
                }
            }
        }

        return null;
    }

    /**
     * @param  mixed  $raw
     * @return array<int,array<string,mixed>>
     */
    protected function extractAccountEntries(mixed $raw): array
    {
        if (is_array($raw) && array_is_list($raw)) {
            return $raw;
        }

        if (is_array($raw) && isset($raw['accounts']) && is_array($raw['accounts'])) {
            return $raw['accounts'];
        }

        return [];
    }
}

