<?php

declare(strict_types=1);

namespace Drupal\Tests\visitors\Unit\Service;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Config\ImmutableConfig;
use Drupal\Tests\UnitTestCase;
use Drupal\visitors\Service\AiAssistantsService;

/**
 * Tests the AiAssistantsService class.
 *
 * @group visitors
 * @coversDefaultClass \Drupal\visitors\Service\AiAssistantsService
 */
class AiAssistantsServiceTest extends UnitTestCase {

  /**
   * The config factory mock.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  private ConfigFactoryInterface $configFactory;

  /**
   * The immutable config mock.
   *
   * @var \Drupal\Core\Config\ImmutableConfig|\PHPUnit\Framework\MockObject\MockObject
   */
  private ImmutableConfig $config;

  /**
   * The AI assistants service.
   *
   * @var \Drupal\visitors\Service\AiAssistantsService
   */
  private AiAssistantsService $aiAssistantsService;

  /**
   * {@inheritdoc}
   */
  protected function setUp(): void {
    parent::setUp();

    $this->config = $this->createMock(ImmutableConfig::class);
    $this->configFactory = $this->createMock(ConfigFactoryInterface::class);

    $this->aiAssistantsService = new AiAssistantsService($this->configFactory);
  }

  /**
   * Tests the constructor.
   *
   * @covers \Drupal\visitors\Service\HostnameMatcherServiceBase::__construct
   */
  public function testConstructor(): void {
    $ai_assistants_service = new AiAssistantsService($this->configFactory);
    $this->assertInstanceOf(AiAssistantsService::class, $ai_assistants_service);
  }

  /**
   * Tests successful AI assistant hostname matching.
   *
   * @covers ::match
   */
  public function testMatchSuccess(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'ChatGPT',
          'hosts' => ['chatgpt.com', 'chat.openai.com'],
        ],
        [
          'label' => 'Copilot',
          'hosts' => ['copilot.microsoft.com'],
        ],
      ],
    ];

    $this->config->expects($this->once())
      ->method('get')
      ->with('sites')
      ->willReturn($configData['sites']);

    $this->configFactory->expects($this->once())
      ->method('get')
      ->with('visitors.ai_assistants')
      ->willReturn($this->config);

    $result = $this->aiAssistantsService->match('chatgpt.com');
    $this->assertEquals('ChatGPT', $result);

    $result = $this->aiAssistantsService->match('chat.openai.com');
    $this->assertEquals('ChatGPT', $result);

    $result = $this->aiAssistantsService->match('copilot.microsoft.com');
    $this->assertEquals('Copilot', $result);
  }

  /**
   * Tests failed AI assistant hostname matching.
   *
   * @covers ::match
   */
  public function testMatchFailure(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'ChatGPT',
          'hosts' => ['chatgpt.com'],
        ],
      ],
    ];

    $this->config->expects($this->once())
      ->method('get')
      ->with('sites')
      ->willReturn($configData['sites']);

    $this->configFactory->expects($this->once())
      ->method('get')
      ->with('visitors.ai_assistants')
      ->willReturn($this->config);

    $result = $this->aiAssistantsService->match('nonexistent.com');
    $this->assertNull($result);
  }

  /**
   * Tests case-insensitive hostname matching.
   *
   * @covers ::match
   */
  public function testMatchCaseInsensitive(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'ChatGPT',
          'hosts' => ['chatgpt.com'],
        ],
      ],
    ];

    $this->config->expects($this->once())
      ->method('get')
      ->with('sites')
      ->willReturn($configData['sites']);

    $this->configFactory->expects($this->once())
      ->method('get')
      ->with('visitors.ai_assistants')
      ->willReturn($this->config);

    $result = $this->aiAssistantsService->match('CHATGPT.COM');
    $this->assertEquals('ChatGPT', $result);
  }

  /**
   * Tests empty configuration handling.
   *
   * @covers ::match
   */
  public function testEmptyConfiguration(): void {
    $this->config->expects($this->once())
      ->method('get')
      ->with('sites')
      ->willReturn([]);

    $this->configFactory->expects($this->once())
      ->method('get')
      ->with('visitors.ai_assistants')
      ->willReturn($this->config);

    $result = $this->aiAssistantsService->match('chatgpt.com');
    $this->assertNull($result);
  }

  /**
   * Tests configuration caching.
   *
   * @covers ::match
   */
  public function testConfigurationCaching(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'ChatGPT',
          'hosts' => ['chatgpt.com'],
        ],
      ],
    ];

    $this->config->expects($this->once())
      ->method('get')
      ->with('sites')
      ->willReturn($configData['sites']);

    $this->configFactory->expects($this->once())
      ->method('get')
      ->with('visitors.ai_assistants')
      ->willReturn($this->config);

    // First call should load config.
    $result1 = $this->aiAssistantsService->match('chatgpt.com');
    $this->assertEquals('ChatGPT', $result1);

    // Second call should use cached config.
    $result2 = $this->aiAssistantsService->match('chatgpt.com');
    $this->assertEquals('ChatGPT', $result2);
  }

  /**
   * Tests the getConfig method directly.
   *
   * @covers ::getConfig
   */
  public function testGetConfig(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'ChatGPT',
          'hosts' => ['chatgpt.com', 'chat.openai.com'],
        ],
        [
          'label' => 'Copilot',
          'hosts' => ['copilot.microsoft.com'],
        ],
      ],
    ];

    $this->config->expects($this->once())
      ->method('get')
      ->with('sites')
      ->willReturn($configData['sites']);

    $this->configFactory->expects($this->once())
      ->method('get')
      ->with('visitors.ai_assistants')
      ->willReturn($this->config);

    // Use reflection to access the protected getConfig method.
    $reflection = new \ReflectionClass($this->aiAssistantsService);
    $method = $reflection->getMethod('getConfig');
    $method->setAccessible(TRUE);

    $result = $method->invoke($this->aiAssistantsService);

    $this->assertEquals($configData['sites'], $result);
  }

  /**
   * Tests the getConfig method with empty configuration.
   *
   * @covers ::getConfig
   */
  public function testGetConfigEmpty(): void {
    $this->config->expects($this->once())
      ->method('get')
      ->with('sites')
      ->willReturn([]);

    $this->configFactory->expects($this->once())
      ->method('get')
      ->with('visitors.ai_assistants')
      ->willReturn($this->config);

    // Use reflection to access the protected getConfig method.
    $reflection = new \ReflectionClass($this->aiAssistantsService);
    $method = $reflection->getMethod('getConfig');
    $method->setAccessible(TRUE);

    $result = $method->invoke($this->aiAssistantsService);

    $this->assertEquals([], $result);
  }

  /**
   * Tests the getConfig method with NULL configuration.
   *
   * @covers ::getConfig
   */
  public function testGetConfigNull(): void {
    $this->config->expects($this->once())
      ->method('get')
      ->with('sites')
      ->willReturn(NULL);

    $this->configFactory->expects($this->once())
      ->method('get')
      ->with('visitors.ai_assistants')
      ->willReturn($this->config);

    // Use reflection to access the protected getConfig method.
    $reflection = new \ReflectionClass($this->aiAssistantsService);
    $method = $reflection->getMethod('getConfig');
    $method->setAccessible(TRUE);

    $result = $method->invoke($this->aiAssistantsService);

    $this->assertEquals([], $result);
  }

}
