<?php

declare(strict_types=1);

namespace Drupal\Tests\flowdrop\Kernel;

use Drupal\Core\Database\Database;
use Drupal\Core\DrupalKernel;
use Drupal\Core\Extension\ExtensionDiscovery;
use Drupal\Core\Site\Settings;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Route;
use Drupal\Core\Routing\RouteObjectInterface;

/**
 * Fixes DrupalKernel app_root resolution in ddev-drupal-contrib environments.
 *
 * In a ddev-drupal-contrib setup, DrupalKernel::guessApplicationRoot()
 * resolves to vendor/drupal/ instead of the web root because DrupalKernel.php
 * lives in vendor/drupal/core/lib/. However, KernelTestBase correctly resolves
 * $this->root from the test bootstrap.
 *
 * This trait overrides bootKernel() to pass $this->root as the $app_root
 * parameter to DrupalKernel's constructor, which is the only change from the
 * parent implementation.
 *
 * Use this trait in any kernel test class (extending KernelTestBase or
 * EntityKernelTestBase) that needs to work in ddev-drupal-contrib.
 */
trait FlowDropKernelTestTrait {

  /**
   * {@inheritdoc}
   *
   * Passes $this->root to DrupalKernel constructor to fix app_root resolution.
   */
  protected function bootKernel() {
    $this->setSetting('container_yamls', []);
    $settings_services_file = $this->root . '/sites/default/testing.services.yml';
    if (file_exists($settings_services_file)) {
      $testing_services_file = $this->siteDirectory . '/services.yml';
      copy($settings_services_file, $testing_services_file);
      $this->setSetting('container_yamls', [$testing_services_file]);
    }

    if (file_exists($test_env = $this->root . '/sites/default/testing.services.yml')) {
      $GLOBALS['conf']['container_yamls']['testing'] = $test_env;
    }
    $GLOBALS['conf']['container_service_providers']['test'] = $this;

    $modules = self::getModulesToEnable(static::class);

    $connection_info = Database::getConnectionInfo();
    $namespace = $connection_info['default']['namespace'] ?? '';
    $autoload = $connection_info['default']['autoload'] ?? '';
    if (str_contains($autoload, 'src/Driver/Database/')) {
      [$first, $second] = explode('\\', $namespace, 3);
      if ($first === 'Drupal' && strtolower($second) === $second) {
        array_unshift($modules, $second);
      }
    }

    // FIX: Pass $this->root as app_root (4th parameter) to DrupalKernel.
    // Without this, guessApplicationRoot() returns vendor/drupal/ instead
    // of the correct web root in ddev-drupal-contrib setups.
    $kernel = new DrupalKernel('testing', $this->classLoader, FALSE, $this->root);
    $kernel->setSitePath($this->siteDirectory);

    $extensions = $modules ? $this->flowDropGetExtensionsForModules($modules) : [];
    $kernel->updateModules($extensions, $extensions);

    $request = Request::create('/');
    $kernel->boot();
    $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('<none>'));
    $request->attributes->set(RouteObjectInterface::ROUTE_NAME, '<none>');
    $kernel->preHandle($request);

    $this->container = $kernel->getContainer();

    $installer_class = $namespace . "\\Install\\Tasks";
    $errors = (new $installer_class())->runTasks();
    if (!empty($errors)) {
      $this->fail('Failed to run installer database tasks: ' . implode(', ', $errors));
    }

    \Drupal::destination()->set('/');

    $this->container->get('config.storage')->write('core.extension', [
      'module' => array_fill_keys($modules, 0),
      'theme' => [],
    ]);

    $settings = Settings::getAll();
    $settings['php_storage']['default'] = [
      'class' => '\Drupal\Component\PhpStorage\FileStorage',
    ];
    new Settings($settings);

    $GLOBALS['config']['system.mail']['interface']['default'] = 'test_mail_collector';
    $GLOBALS['config']['system.mail']['mailer_dsn'] = [
      'scheme' => 'null',
      'host' => 'null',
      'user' => NULL,
      'password' => NULL,
      'port' => NULL,
      'options' => [],
    ];
    $GLOBALS['config']['system.file']['default_scheme'] = 'public';
  }

  /**
   * Discovers extensions for the given module names.
   *
   * Duplicates KernelTestBase::getExtensionsForModules() which is private.
   *
   * @param array $modules
   *   Module names to discover.
   *
   * @return array
   *   Extension objects keyed by module name.
   */
  private function flowDropGetExtensionsForModules(array $modules): array {
    $extensions = [];
    $discovery = new ExtensionDiscovery($this->root);
    $discovery->setProfileDirectories([]);
    $list = $discovery->scan('module');
    foreach ($modules as $name) {
      if (!isset($list[$name])) {
        throw new \Exception("Unavailable module: '$name'. If this module needs to be downloaded for testing, include it in the 'require-dev' section of your composer.json file.");
      }
      $extensions[$name] = $list[$name];
    }
    return $extensions;
  }

}
