<?php

declare(strict_types=1);

namespace apexl\Io\modules\user\services\twoFactorAuth;

use apexl\Config\ConfigurationInterface;
use apexl\encryption\Encrypt;
use apexl\Io\modules\user\entities\TwoFactorMethodEntity;
use apexl\Io\modules\user\exceptions\TwoFactorException;
use Throwable;

/**
 * Encrypts / decrypts a 2FA method's shared secret at rest, using
 * apexl/hashing's Encrypt (AES-256-CBC, random IV per secret). The key comes
 * from `auth.two_factor.encryption_key` (which itself falls back to the JWT
 * secret). Reused, not reinvented — no new crypto package (Plan 007 §5, §8).
 */
final readonly class TwoFactorSecretCipher
{
    public function __construct(
        private ConfigurationInterface $config,
        private Encrypt $encrypt,
    ) {}

    /** Encrypt a plaintext secret, writing `secret` + `secret_iv` onto the method. */
    public function store(TwoFactorMethodEntity $method, string $plainSecret): void
    {
        [$iv, $cipherText] = $this->encrypt->encrypt($plainSecret, $this->key());
        $method->secret = $cipherText;
        $method->secret_iv = $iv;
    }

    /**
     * Decrypt the secret held on a method.
     *
     * @throws TwoFactorException when the row carries no secret (mis-provisioned).
     */
    public function reveal(TwoFactorMethodEntity $method): string
    {
        // Reading an unloaded field on a fresh entity would raise a lazy-load
        // error; treat any absent/unset secret uniformly as "no stored secret".
        try {
            $secret = $method->secret;
            $iv = $method->secret_iv;
        } catch (Throwable) {
            $secret = null;
            $iv = null;
        }

        if (!$secret || !$iv) {
            throw new TwoFactorException('Two-factor method has no stored secret.');
        }

        $plain = $this->encrypt->decrypt($secret, $iv, $this->key());

        if ($plain === false || $plain === null || $plain === '') {
            throw new TwoFactorException('Unable to decrypt the two-factor secret.');
        }

        return $plain;
    }

    private function key(): string
    {
        $key = (string) ($this->config->get('auth.two_factor.encryption_key') ?? '');

        if ($key === '') {
            // Never silently encrypt with an empty key — that would be trivially
            // reversible and a false sense of security.
            throw new TwoFactorException(
                'No two-factor encryption key configured (auth.two_factor.encryption_key / AUTH_JWT_SECRET_KEY).'
            );
        }

        return $key;
    }
}
