<?php

namespace App\Services\Stripe;

use App\Services\Secrets\SecretVault;
use App\Support\OperatingMode;
use Illuminate\Support\Facades\Http;
use Throwable;

/**
 * Read-only answers to "does this key actually work, and what is it wired to?"
 *
 * Separate from StripeClient, which exists to create and archive things. A key
 * that is saved but wrong is worse than no key, because the failure surfaces
 * later, somewhere else, and usually to a customer — so it can be checked at
 * the moment it is entered.
 *
 * Reports the account and the LIVE/TEST mode, because the single most expensive
 * mistake here is pasting a test key into a live installation, or the reverse,
 * and both look identical in a form.
 */
final class StripeCheck
{
    /** @return array<string, mixed> */
    public function run(?string $candidate = null): array
    {
        $secret = $candidate ?: app(SecretVault::class)->get('stripe.secret');

        if (blank($secret)) {
            return ['ok' => false, 'reason' => 'missing'];
        }

        try {
            $account = Http::withToken($secret)->acceptJson()->timeout(15)
                ->get($this->url('account'));
        } catch (Throwable) {
            return ['ok' => false, 'reason' => 'unreachable'];
        }

        if ($account->status() === 401) {
            return ['ok' => false, 'reason' => 'rejected'];
        }

        if (! $account->successful()) {
            return ['ok' => false, 'reason' => 'error', 'status' => $account->status()];
        }

        return [
            'ok' => true,
            'account' => $account->json('id'),
            'business' => $account->json('settings.dashboard.display_name') ?: $account->json('business_profile.name'),
            // Stripe does not label the key; the mode is inferred from the
            // prefix, which is the only thing that is true of both key kinds.
            // Asked of OperatingMode, not spelled out again here: the same
            // question decides which slot the migration files a key into and
            // whether the readiness page reports a key sitting in the wrong
            // one. Unrecognisable (null) stays false, exactly as before.
            'live' => OperatingMode::ofStripeKey($secret) === OperatingMode::Live,
            'restricted' => str_starts_with($secret, 'rk_'),
            'webhooks' => $this->webhooks($secret),
        ];
    }

    /**
     * The configured webhook endpoints and which events each one subscribes to.
     *
     * This is the part nobody can see from the outside: a key can be perfectly
     * valid while the endpoint listens for the wrong five events, and nothing
     * fails until a payment is not recorded.
     *
     * @return array<int, array<string, mixed>>|null null when the key may not read them
     */
    private function webhooks(string $secret): ?array
    {
        try {
            $response = Http::withToken($secret)->acceptJson()->timeout(15)
                ->get($this->url('webhook_endpoints'), ['limit' => 10]);
        } catch (Throwable) {
            return null;
        }

        if (! $response->successful()) {
            // A restricted key legitimately cannot list these. Saying "unknown"
            // beats reporting "none configured", which would send someone
            // hunting for a problem that is not there.
            return null;
        }

        return collect($response->json('data', []))
            ->map(fn (array $endpoint) => [
                'url' => $endpoint['url'] ?? '—',
                'status' => $endpoint['status'] ?? 'unknown',
                'events' => $endpoint['enabled_events'] ?? [],
            ])
            ->all();
    }

    private function url(string $path): string
    {
        return rtrim((string) config('services.stripe.api_base', 'https://api.stripe.com/v1'), '/').'/'.$path;
    }
}
