<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop\Kernel\Bundle;

use Drupal\KernelTests\KernelTestBase;
use Drupal\flowdrop\Entity\FlowDropTrustedPublisher;
use Drupal\flowdrop\Service\Bundle\BundleCanonicalizer;
use Drupal\flowdrop\Service\Bundle\BundleSigner;
use Drupal\flowdrop\Service\Bundle\TrustedPublisherResolverInterface;
use Drupal\flowdrop\Service\Bundle\VerificationResult;

/**
 * Tests the config-entity-backed trusted publisher resolver and verifier.
 *
 * @group flowdrop
 * @covers \Drupal\flowdrop\Service\Bundle\TrustedPublisherResolver
 * @covers \Drupal\flowdrop\Entity\FlowDropTrustedPublisher
 */
class TrustedPublisherResolverTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = ['flowdrop'];

  /**
   * The raw Ed25519 public key.
   */
  private string $publicKey;

  /**
   * The raw Ed25519 secret key.
   */
  private string $secretKey;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();
    $this->installConfig(['flowdrop']);
    $keypair = sodium_crypto_sign_keypair();
    $this->publicKey = sodium_crypto_sign_publickey($keypair);
    $this->secretKey = sodium_crypto_sign_secretkey($keypair);
  }

  /**
   * An enabled publisher's key resolves; a verifier trusts its bundle.
   */
  public function testEnabledPublisherResolvesAndVerifies(): void {
    $this->createPublisher('acme', TRUE);

    $resolver = $this->container->get(TrustedPublisherResolverInterface::class);
    $this->assertSame($this->publicKey, $resolver->getPublicKey('acme'));

    /** @var \Drupal\flowdrop\Service\Bundle\BundleVerifier $verifier */
    $verifier = $this->container->get('flowdrop.bundle_verifier');
    $bundle = $this->signedBundle('acme');
    $this->assertSame(VerificationResult::TRUSTED, $verifier->verify($bundle)->getStatus());
  }

  /**
   * A disabled publisher does not resolve, so its bundle is untrusted.
   */
  public function testDisabledPublisherIsUntrusted(): void {
    $this->createPublisher('acme', FALSE);

    $resolver = $this->container->get(TrustedPublisherResolverInterface::class);
    $this->assertNull($resolver->getPublicKey('acme'));

    /** @var \Drupal\flowdrop\Service\Bundle\BundleVerifier $verifier */
    $verifier = $this->container->get('flowdrop.bundle_verifier');
    $this->assertSame(VerificationResult::UNTRUSTED, $verifier->verify($this->signedBundle('acme'))->getStatus());
  }

  /**
   * An unknown publisher resolves to NULL.
   */
  public function testUnknownPublisherResolvesToNull(): void {
    $resolver = $this->container->get(TrustedPublisherResolverInterface::class);
    $this->assertNull($resolver->getPublicKey('nobody'));
  }

  /**
   * Creates a trusted publisher entity.
   *
   * @param string $id
   *   The publisher key id.
   * @param bool $enabled
   *   Whether the publisher is enabled.
   */
  private function createPublisher(string $id, bool $enabled): void {
    FlowDropTrustedPublisher::create([
      'id' => $id,
      'label' => ucfirst($id),
      'public_key' => base64_encode($this->publicKey),
      'enabled' => $enabled,
    ])->save();
  }

  /**
   * Builds a bundle signed by the test keypair.
   *
   * @param string $publisher
   *   The publisher key id.
   *
   * @return array
   *   The signed bundle.
   */
  private function signedBundle(string $publisher): array {
    $signer = new BundleSigner(new BundleCanonicalizer());
    return $signer->sign([
      'publisher' => $publisher,
      'flow' => ['id' => 'demo', 'label' => 'Demo', 'nodes' => [], 'edges' => []],
    ], $publisher, $this->secretKey);
  }

}
