<?php

namespace App\Services\Platforms\Support;

use App\Models\ApiCall;
use App\Services\Meta\MetaException;

/**
 * Limits that hold regardless of what the model decides or the user types.
 *
 * Checked inside the API client, below the agent and below the controller, so
 * there is no path around them.
 */
class Guardrails
{
    /**
     * Refuse any ad account that is not explicitly allowed, before a single
     * call is made. Reads included: an account we may not spend on is an
     * account we have no business reading either.
     *
     * @throws MetaException
     */
    public function assertAccountAllowed(string $adAccountId): void
    {
        $allowed = (array) config('platforms.guardrails.allowed_ad_accounts');
        $account = $this->normalise($adAccountId);

        if ($allowed === []) {
            throw new MetaException(
                'No ad accounts are allowed. Set ARB_ALLOWED_AD_ACCOUNTS before ARB can reach Meta.'
            );
        }

        if (! in_array($account, $allowed, true)) {
            throw new MetaException(sprintf(
                '%s is not on the allowed list. Permitted: %s',
                $account,
                implode(', ', $allowed),
            ));
        }
    }

    /**
     * Stay inside a share of Meta's hourly limit, so anything else using the
     * same token keeps its own headroom.
     *
     * @throws MetaException
     */
    public function assertWithinCallBudget(?string $adAccountId): void
    {
        if (! $adAccountId) {
            return;
        }

        $limit = (int) config('platforms.guardrails.max_calls_per_hour');

        $used = ApiCall::query()
            ->where('ad_account_id', $adAccountId)
            ->where('created_at', '>=', now()->subHour())
            ->count();

        if ($used >= $limit) {
            throw new MetaException(sprintf(
                'ARB has used its hourly call budget for %s (%d of %d). Pausing so other systems keep their share.',
                $adAccountId,
                $used,
                $limit,
            ));
        }
    }

    /** Remove credentials and truncate anything oversized before logging. */
    public function scrub(array $payload): array
    {
        unset($payload['access_token']);

        return array_map(
            fn (mixed $value): mixed => is_string($value) && strlen($value) > 500
                ? substr($value, 0, 500).sprintf('… [%d chars]', strlen($value))
                : $value,
            $payload,
        );
    }

    private function normalise(string $adAccountId): string
    {
        return str_starts_with($adAccountId, 'act_') ? $adAccountId : 'act_'.$adAccountId;
    }
}
