<?php

namespace Autogedal\TbiBank\Test\Unit;

use Autogedal\TbiBank\Model\Config\Backend\EncryptionKey;
use Autogedal\TbiBank\Model\KeyValue;
use Magento\Framework\App\DeploymentConfig;
use Magento\Framework\Encryption\Encryptor;
use Magento\Framework\Encryption\EncryptorInterface;
use Magento\Framework\Encryption\KeyValidator;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Math\Random;
use PHPUnit\Framework\TestCase;

class EncryptionKeyTest extends TestCase
{
    private function encryptor(): Encryptor
    {
        $deployment = $this->createMock(DeploymentConfig::class);
        $deployment->method('get')->with(Encryptor::PARAM_CRYPT_KEY)->willReturn(str_repeat('a', 32));
        return new Encryptor(new Random(), $deployment, new KeyValidator());
    }

    private function backend(EncryptorInterface $encryptor): EncryptionKey
    {
        // Exercise the actual inherited save implementation without a DB-backed model constructor.
        return new class ($encryptor) extends EncryptionKey {
            public function __construct(EncryptorInterface $encryptor)
            {
                $this->_encryptor = $encryptor;
            }

            public function loadValueForTest(string $value): void
            {
                $this->setValue($value);
                $this->_afterLoad();
            }

            public function isWriteAllowedForTest(): bool
            {
                return $this->_dataSaveAllowed;
            }
        };
    }

    public function testNewKeyIsEncryptedOnSaveAndDecryptedOnceOnRead(): void
    {
        $encryptor = $this->encryptor();
        $backend = $this->backend($encryptor);
        $plain = 'synthetic-TBI-key!for-tests';
        $backend->setValue($plain);
        $backend->beforeSave();
        $stored = $backend->getValue();
        $this->assertNotSame($plain, $stored);
        $this->assertSame($plain, $encryptor->decrypt($stored));
        $processed = $backend->processValue($stored);
        $this->assertSame($plain, $processed);
        $this->assertSame($plain, KeyValue::decode($processed, $encryptor));
        $backend->loadValueForTest($stored);
        $this->assertSame($plain, $backend->getValue());
    }

    public function testLegacyPlaintextSurvivesConfigProcessingAndAdminLoad(): void
    {
        $encryptor = $this->createMock(EncryptorInterface::class);
        $encryptor->expects($this->never())->method('decrypt');
        $backend = $this->backend($encryptor);
        $plain = 'legacy-key!with:punctuation';
        $this->assertSame($plain, $backend->processValue($plain));
        $backend->loadValueForTest($plain);
        $this->assertSame($plain, $backend->getValue());
        $this->assertSame('', $backend->processValue(null));
    }

    public function testMaskedAdminValueDoesNotOverwriteOrEncryptExistingKey(): void
    {
        $encryptor = $this->createMock(EncryptorInterface::class);
        $encryptor->expects($this->never())->method('encrypt');
        $backend = $this->backend($encryptor);
        $backend->setValue('******');
        $backend->beforeSave();
        $this->assertFalse($backend->isWriteAllowedForTest());
    }

    public function testUndecryptableCiphertextNeverFallsBackToCiphertext(): void
    {
        $encryptor = $this->createMock(EncryptorInterface::class);
        $encryptor->method('decrypt')->willReturn('');
        $this->expectException(LocalizedException::class);
        KeyValue::decode('0:3:invalid-payload', $encryptor);
    }

    public function testDecryptExceptionDoesNotExposeStoredValue(): void
    {
        $encryptor = $this->createMock(EncryptorInterface::class);
        $encryptor->method('decrypt')->willThrowException(new \RuntimeException('secret-bearing-error'));
        try {
            KeyValue::decode('0:3:invalid-payload', $encryptor);
            $this->fail('Expected encrypted-key failure.');
        } catch (LocalizedException $exception) {
            $this->assertStringNotContainsString('secret-bearing-error', $exception->getMessage());
            $this->assertStringNotContainsString('invalid-payload', $exception->getMessage());
        }
    }
}
