<?php

namespace App\Services\Providers;

use App\Models\ProviderConnection;
use App\Models\ProviderCredential;
use App\Support\Providers\ProviderConnectionType;
use App\Support\Providers\ProviderCredentialKind;
use App\Support\Providers\ProviderCredentialSource;
use InvalidArgumentException;
use RuntimeException;

final class CredentialManager
{
    /**
     * @return array{client_id: string, client_secret: string}
     */
    public function getClientCredentials(ProviderConnection $connection): array
    {
        if ($connection->connection_type !== ProviderConnectionType::Dedicated) {
            throw new InvalidArgumentException('Dedicated provider credentials are only available for dedicated connections.');
        }

        $credential = $connection->credential;

        if (! $credential instanceof ProviderCredential) {
            throw new RuntimeException('Provider credentials are missing.');
        }

        if ($credential->type !== 'client_secret') {
            throw new RuntimeException('Unsupported provider credential type.');
        }

        $payload = $credential->payload;

        if (! is_array($payload)) {
            throw new RuntimeException('Provider credential payload is invalid.');
        }

        $clientId = trim((string) ($payload['client_id'] ?? ''));
        $clientSecret = trim((string) ($payload['client_secret'] ?? ''));

        if ($clientId === '' || $clientSecret === '') {
            throw new RuntimeException('Provider credential payload is missing required keys.');
        }

        $targetScopeIdentifier = $payload['managed_environment_id'] ?? null;

        if (
            is_string($targetScopeIdentifier)
            && $targetScopeIdentifier !== ''
            && $targetScopeIdentifier !== $connection->entra_tenant_id
        ) {
            throw new InvalidArgumentException('Provider credential target scope does not match the connection target scope.');
        }

        return [
            'client_id' => $clientId,
            'client_secret' => $clientSecret,
        ];
    }

    public function upsertClientSecretCredential(
        ProviderConnection $connection,
        string $clientId,
        string $clientSecret,
        ProviderCredentialSource $source = ProviderCredentialSource::DedicatedManual,
    ): ProviderCredential {
        $clientId = trim($clientId);
        $clientSecret = trim($clientSecret);

        if ($clientId === '' || $clientSecret === '') {
            throw new InvalidArgumentException('client_id and client_secret are required.');
        }

        $existing = $connection->credential;
        $existingPayload = $existing instanceof ProviderCredential && is_array($existing->payload)
            ? $existing->payload
            : [];
        $secretChanged = ! $existing instanceof ProviderCredential
            || trim((string) ($existingPayload['client_secret'] ?? '')) !== $clientSecret;

        return ProviderCredential::query()->updateOrCreate(
            [
                'provider_connection_id' => $connection->getKey(),
            ],
            [
                'type' => 'client_secret',
                'credential_kind' => ProviderCredentialKind::ClientSecret->value,
                'source' => $source->value,
                'last_rotated_at' => $secretChanged ? now() : $existing?->last_rotated_at,
                'expires_at' => $existing?->expires_at,
                'payload' => [
                    'client_id' => $clientId,
                    'client_secret' => $clientSecret,
                ],
            ],
        );
    }

    public function updateClientIdPreservingSecret(ProviderConnection $connection, string $clientId): ProviderCredential
    {
        $clientId = trim($clientId);

        if ($clientId === '') {
            throw new InvalidArgumentException('client_id is required.');
        }

        $existing = $this->getClientCredentials($connection);
        $source = $connection->credential?->source instanceof ProviderCredentialSource
            ? $connection->credential->source
            : ProviderCredentialSource::DedicatedManual;

        return $this->upsertClientSecretCredential(
            connection: $connection,
            clientId: $clientId,
            clientSecret: (string) $existing['client_secret'],
            source: $source,
        );
    }
}
