<?php

namespace App\Services\Secrets;

use Illuminate\Encryption\Encrypter;
use RuntimeException;

/**
 * The SECRETS_KEY encrypter, in one place.
 *
 * Its own key rather than APP_KEY, because rotating APP_KEY is ordinary
 * maintenance and would otherwise render every stored credential unreadable —
 * discovered when Stripe stops answering, not when it happens.
 *
 * Extracted from SecretVault so mailbox passwords use the SAME key handling
 * rather than a second copy of it. The copy is what would rot: the two
 * spellings below exist because requiring the `base64:` prefix once made every
 * read and write fail on exactly the setup the config comment documents.
 */
final class SecretCipher
{
    public function encrypt(string $plain): string
    {
        return $this->encrypter()->encryptString($plain);
    }

    public function decrypt(string $cipher): string
    {
        return $this->encrypter()->decryptString($cipher);
    }

    /**
     * Is storing credentials possible at all on this installation?
     *
     * Delegates to the exact same resolveKey() that encrypter() below checks
     * its input against, rather than a second, shorter rule that only looks
     * at emptiness. A malformed-but-nonempty key (wrong length, garbage
     * base64) used to pass a bare "!== ''" check while encrypter() rejected
     * it — which let EditMailbox::save() and MailboxTester::run() sail past
     * the very guard they call this method for, straight into the uncaught
     * RuntimeException it exists to prevent.
     */
    public function isUsable(): bool
    {
        return $this->resolveKey() !== null;
    }

    /**
     * The raw, exactly-32-byte key — or null if SECRETS_KEY is empty or
     * malformed. The one place that decides "is this key any good", so
     * isUsable() and encrypter() read it from the same rule and cannot
     * disagree about a value either one is given.
     */
    private function resolveKey(): ?string
    {
        $key = (string) config('admin_access.secrets_key');

        if ($key === '') {
            return null;
        }

        if (str_starts_with($key, 'base64:')) {
            $key = substr($key, 7);
        }

        if (strlen($key) !== 32) {
            $decoded = base64_decode($key, true);

            if ($decoded !== false && strlen($decoded) === 32) {
                $key = $decoded;
            }
        }

        return strlen($key) === 32 ? $key : null;
    }

    private function encrypter(): Encrypter
    {
        $key = $this->resolveKey();

        if ($key === null) {
            throw new RuntimeException(
                (string) config('admin_access.secrets_key') === ''
                    ? 'SECRETS_KEY is not set — refusing to store or read credentials.'
                    : 'SECRETS_KEY must be 32 bytes (or base64 of 32 bytes).'
            );
        }

        return new Encrypter($key, 'aes-256-cbc');
    }
}
