<?php

namespace Drupal\Tests\eb\Traits;

use Symfony\Component\Yaml\Yaml;

/**
 * Provides helper methods for working with YAML fixtures in tests.
 *
 * Fixtures are organized into:
 * - tests/fixtures/valid/ - Valid YAML definitions for positive testing.
 * - tests/fixtures/invalid/ - Invalid YAML definitions for validation testing.
 */
trait YamlFixtureTrait {

  /**
   * Gets the base path to the eb module's fixtures directory.
   *
   * @return string
   *   The fixtures directory path.
   */
  protected function getFixturesBasePath(): string {
    // Try to get module path from container.
    // @phpstan-ignore isset.property, booleanAnd.rightAlwaysTrue
    if (isset($this->container) && $this->container->has('extension.path.resolver')) {
      $modulePath = $this->container
        ->get('extension.path.resolver')
        ->getPath('module', 'eb');
    }
    else {
      // Fallback for unit tests.
      $modulePath = dirname(__DIR__, 3);
    }

    return "$modulePath/tests/fixtures";
  }

  /**
   * Loads a valid YAML fixture file.
   *
   * @param string $filename
   *   The fixture filename (without path, e.g., 'simple_bundle.yml').
   *
   * @return array<string, mixed>
   *   The parsed YAML data.
   *
   * @throws \RuntimeException
   *   If the fixture file does not exist.
   */
  protected function loadValidFixture(string $filename): array {
    $path = $this->getFixturesBasePath() . "/valid/$filename";
    return $this->parseYamlFile($path);
  }

  /**
   * Loads an invalid YAML fixture file.
   *
   * @param string $filename
   *   The fixture filename (without path, e.g., 'missing_version.yml').
   *
   * @return array<string, mixed>
   *   The parsed YAML data.
   *
   * @throws \RuntimeException
   *   If the fixture file does not exist.
   */
  protected function loadInvalidFixture(string $filename): array {
    $path = $this->getFixturesBasePath() . "/invalid/$filename";
    return $this->parseYamlFile($path);
  }

  /**
   * Gets the full path to a valid fixture file.
   *
   * @param string $filename
   *   The fixture filename.
   *
   * @return string
   *   The full path to the fixture.
   */
  protected function getValidFixturePath(string $filename): string {
    return $this->getFixturesBasePath() . "/valid/$filename";
  }

  /**
   * Gets the full path to an invalid fixture file.
   *
   * @param string $filename
   *   The fixture filename.
   *
   * @return string
   *   The full path to the fixture.
   */
  protected function getInvalidFixturePath(string $filename): string {
    return $this->getFixturesBasePath() . "/invalid/$filename";
  }

  /**
   * Parses a YAML file and returns its contents.
   *
   * @param string $path
   *   The full path to the YAML file.
   *
   * @return array<string, mixed>
   *   The parsed YAML data.
   *
   * @throws \RuntimeException
   *   If the file does not exist.
   */
  protected function parseYamlFile(string $path): array {
    if (!file_exists($path)) {
      throw new \RuntimeException("Fixture file not found: $path");
    }

    $content = file_get_contents($path);
    return Yaml::parse($content) ?? [];
  }

  /**
   * Creates a temporary YAML file from an array.
   *
   * Useful for creating custom test fixtures dynamically.
   *
   * @param array<string, mixed> $data
   *   The data to serialize to YAML.
   * @param string $filename
   *   Optional filename. If empty, a unique name is generated.
   *
   * @return string
   *   The path to the created temporary file.
   */
  protected function createTempYamlFile(array $data, string $filename = ''): string {
    if (empty($filename)) {
      $filename = 'test_' . uniqid() . '.yml';
    }

    $path = sys_get_temp_dir() . '/eb_test/' . $filename;
    $dir = dirname($path);

    if (!is_dir($dir)) {
      mkdir($dir, 0755, TRUE);
    }

    $content = Yaml::dump($data, 10, 2);
    file_put_contents($path, $content);

    return $path;
  }

  /**
   * Creates a temporary YAML file from raw YAML string.
   *
   * @param string $yaml
   *   The YAML content.
   * @param string $filename
   *   Optional filename. If empty, a unique name is generated.
   *
   * @return string
   *   The path to the created temporary file.
   */
  protected function createTempYamlFromString(string $yaml, string $filename = ''): string {
    if (empty($filename)) {
      $filename = 'test_' . uniqid() . '.yml';
    }

    $path = sys_get_temp_dir() . '/eb_test/' . $filename;
    $dir = dirname($path);

    if (!is_dir($dir)) {
      mkdir($dir, 0755, TRUE);
    }

    file_put_contents($path, $yaml);

    return $path;
  }

  /**
   * Cleans up temporary files created during tests.
   *
   * Call this in tearDown() if using createTempYamlFile().
   */
  protected function cleanupTempYamlFiles(): void {
    $tempDir = sys_get_temp_dir() . '/eb_test';
    if (is_dir($tempDir)) {
      $files = new \RecursiveIteratorIterator(
        new \RecursiveDirectoryIterator($tempDir, \RecursiveDirectoryIterator::SKIP_DOTS),
        \RecursiveIteratorIterator::CHILD_FIRST
      );

      foreach ($files as $file) {
        if ($file->isDir()) {
          rmdir($file->getRealPath());
        }
        else {
          unlink($file->getRealPath());
        }
      }

      rmdir($tempDir);
    }
  }

  /**
   * Lists all available valid fixtures.
   *
   * @return array<string>
   *   Array of fixture filenames.
   */
  protected function listValidFixtures(): array {
    $path = $this->getFixturesBasePath() . '/valid';
    return $this->listYamlFiles($path);
  }

  /**
   * Lists all available invalid fixtures.
   *
   * @return array<string>
   *   Array of fixture filenames.
   */
  protected function listInvalidFixtures(): array {
    $path = $this->getFixturesBasePath() . '/invalid';
    return $this->listYamlFiles($path);
  }

  /**
   * Lists YAML files in a directory.
   *
   * @param string $directory
   *   The directory path.
   *
   * @return array<string>
   *   Array of YAML filenames.
   */
  private function listYamlFiles(string $directory): array {
    if (!is_dir($directory)) {
      return [];
    }

    $files = [];
    foreach (scandir($directory) as $file) {
      if (str_ends_with($file, '.yml') || str_ends_with($file, '.yaml')) {
        $files[] = $file;
      }
    }

    return $files;
  }

}
