<?php

declare(strict_types=1);

namespace Drupal\Tests\nodehive_path_alias\Unit;

use Drupal\Core\Database\Connection;
use Drupal\Core\Database\Query\SelectInterface;
use Drupal\Core\Database\StatementInterface;
use Drupal\nodehive_path_alias\PathAliasSpaceLookup;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;

/**
 * Unit tests for PathAliasSpaceLookup.
 */
#[CoversClass(PathAliasSpaceLookup::class)]
#[Group('nodehive_path_alias')]
final class PathAliasSpaceLookupTest extends TestCase {

  /**
   * Tests that resolve returns the system path when a match is found.
   */
  public function testResolveReturnsSystemPathWhenFound(): void {
    $query = $this->buildQuery('/node/42');

    $db = $this->createMock(Connection::class);
    $db->method('select')->willReturn($query);

    $lookup = new PathAliasSpaceLookup($db);

    $result = $lookup->resolve('/about', 1, 'cs');

    self::assertSame('/node/42', $result);
  }

  /**
   * Tests that resolve returns NULL when no match is found.
   */
  public function testResolveReturnsNullWhenNotFound(): void {
    $query = $this->buildQuery(FALSE);

    $db = $this->createMock(Connection::class);
    $db->method('select')->willReturn($query);

    $lookup = new PathAliasSpaceLookup($db);

    $result = $lookup->resolve('/missing', 1, 'cs');

    self::assertNull($result);
  }

  /**
   * Tests that resolve falls back to 'und' when the given langcode fails.
   */
  public function testResolveFallsBackToUnd(): void {
    // First call (langcode='cs') → FALSE; second call (langcode='und') → found.
    $stmtNotFound = $this->createMock(StatementInterface::class);
    $stmtNotFound->method('fetchField')->willReturn(FALSE);

    $stmtFound = $this->createMock(StatementInterface::class);
    $stmtFound->method('fetchField')->willReturn('/node/7');

    $queryNotFound = $this->createMock(SelectInterface::class);
    $queryNotFound->method('fields')->willReturnSelf();
    $queryNotFound->method('condition')->willReturnSelf();
    $queryNotFound->method('range')->willReturnSelf();
    $queryNotFound->method('execute')->willReturn($stmtNotFound);

    $queryFound = $this->createMock(SelectInterface::class);
    $queryFound->method('fields')->willReturnSelf();
    $queryFound->method('condition')->willReturnSelf();
    $queryFound->method('range')->willReturnSelf();
    $queryFound->method('execute')->willReturn($stmtFound);

    $db = $this->createMock(Connection::class);
    $db->method('select')->willReturnOnConsecutiveCalls($queryNotFound, $queryFound);

    $lookup = new PathAliasSpaceLookup($db);

    $result = $lookup->resolve('/about', 1, 'cs');

    self::assertSame('/node/7', $result);
  }

  /**
   * Tests that a NULL langcode only tries the 'und' langcode.
   */
  public function testResolveWithNullLangcodeOnlyTriesUnd(): void {
    // langcode=null → only 'und' is tried.
    $query = $this->buildQuery('/node/5');

    $db = $this->createMock(Connection::class);
    $db->expects(self::once())->method('select')->willReturn($query);

    $lookup = new PathAliasSpaceLookup($db);

    $result = $lookup->resolve('/page', 2, NULL);

    self::assertSame('/node/5', $result);
  }

  /**
   * Builds a mock SelectInterface that returns the given fetchField value.
   */
  private function buildQuery(mixed $fetchFieldReturn): SelectInterface {
    $stmt = $this->createMock(StatementInterface::class);
    $stmt->method('fetchField')->willReturn($fetchFieldReturn);

    $query = $this->createMock(SelectInterface::class);
    $query->method('fields')->willReturnSelf();
    $query->method('condition')->willReturnSelf();
    $query->method('range')->willReturnSelf();
    $query->method('execute')->willReturn($stmt);

    return $query;
  }

}
