<?php

declare(strict_types=1);

namespace Drupal\Tests\visitors\Kernel\Service;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\KernelTests\KernelTestBase;
use Drupal\visitors\Service\SpamService;
use Drupal\visitors\VisitorsSpamInterface;

/**
 * Kernel tests for the SpamService.
 *
 * @group visitors
 * @coversDefaultClass \Drupal\visitors\Service\SpamService
 */
final class SpamServiceTest extends KernelTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'visitors',
    'user',
  ];

  /**
   * The spam service.
   *
   * @var \Drupal\visitors\Service\SpamService
   */
  private SpamService $spamService;

  /**
   * The configuration factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  private ConfigFactoryInterface $configFactory;

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

    $this->installConfig(['visitors']);
    $this->spamService = $this->container->get('visitors.spam');
    $this->configFactory = $this->container->get('config.factory');
  }

  /**
   * Tests the service is properly registered and injectable.
   *
   * @covers ::__construct
   */
  public function testServiceRegistration(): void {
    $this->assertInstanceOf(SpamService::class, $this->spamService);
    $this->assertInstanceOf(VisitorsSpamInterface::class, $this->spamService);
  }

  /**
   * Tests that the service loads configuration correctly.
   *
   * @covers ::__construct
   */
  public function testConfigurationLoading(): void {
    // Test that configuration is loaded from the default config.
    $config = $this->configFactory->get('visitors.spam');
    $spamSites = $config->get('sites');

    $this->assertIsArray($spamSites);
    $this->assertNotEmpty($spamSites);

    // Verify some known spam sites are in the configuration.
    $this->assertContains('0-0.fr', $spamSites);
    $this->assertContains('007agent-i.fr', $spamSites);
    $this->assertContains('1-88.vip', $spamSites);
  }

  /**
   * Tests exact domain matching.
   *
   * @covers ::match
   * @covers ::isDomainMatch
   */
  public function testExactDomainMatch(): void {
    // Test with a known spam site from the configuration.
    $result = $this->spamService->match('0-0.fr');
    $this->assertTrue($result, 'Exact domain match should return TRUE');

    $result = $this->spamService->match('007agent-i.fr');
    $this->assertTrue($result, 'Exact domain match should return TRUE');

    $result = $this->spamService->match('1-88.vip');
    $this->assertTrue($result, 'Exact domain match should return TRUE');
  }

  /**
   * Tests subdomain matching.
   *
   * @covers ::match
   * @covers ::isDomainMatch
   */
  public function testSubdomainMatch(): void {
    // Test with subdomains of known spam sites.
    $result = $this->spamService->match('subdomain.0-0.fr');
    $this->assertTrue($result, 'Subdomain match should return TRUE');

    $result = $this->spamService->match('www.007agent-i.fr');
    $this->assertTrue($result, 'Subdomain match should return TRUE');

    $result = $this->spamService->match('api.1-88.vip');
    $this->assertTrue($result, 'Subdomain match should return TRUE');
  }

  /**
   * Tests non-matching domains.
   *
   * @covers ::match
   * @covers ::isDomainMatch
   */
  public function testNonMatchingDomains(): void {
    // Test with legitimate domains that should not match.
    $result = $this->spamService->match('google.com');
    $this->assertFalse($result, 'Legitimate domain should return FALSE');

    $result = $this->spamService->match('example.com');
    $this->assertFalse($result, 'Legitimate domain should return FALSE');

    $result = $this->spamService->match('drupal.org');
    $this->assertFalse($result, 'Legitimate domain should return FALSE');
  }

  /**
   * Tests case insensitivity.
   *
   * @covers ::match
   * @covers ::isDomainMatch
   */
  public function testCaseInsensitivity(): void {
    // Test with mixed case domains.
    $result = $this->spamService->match('0-0.FR');
    $this->assertTrue($result, 'Uppercase domain should match');

    $result = $this->spamService->match('007AGENT-I.FR');
    $this->assertTrue($result, 'Uppercase domain should match');

    $result = $this->spamService->match('1-88.VIP');
    $this->assertTrue($result, 'Uppercase domain should match');
  }

  /**
   * Tests domain normalization (trimming).
   *
   * @covers ::match
   * @covers ::isDomainMatch
   */
  public function testDomainNormalization(): void {
    // Test with domains that have leading/trailing whitespace.
    $result = $this->spamService->match(' 0-0.fr ');
    $this->assertTrue($result, 'Domain with whitespace should match');

    $result = $this->spamService->match('  007agent-i.fr  ');
    $this->assertTrue($result, 'Domain with whitespace should match');
  }

  /**
   * Tests edge cases and boundary conditions.
   *
   * @covers ::match
   * @covers ::isDomainMatch
   */
  public function testEdgeCases(): void {
    // Test with empty string.
    $result = $this->spamService->match('');
    $this->assertFalse($result, 'Empty string should return FALSE');

    // Test with very long domain.
    $longDomain = str_repeat('a', 100) . '.com';
    $result = $this->spamService->match($longDomain);
    $this->assertFalse($result, 'Very long domain should return FALSE');

    // Test with domain containing special characters.
    $result = $this->spamService->match('test-domain.com');
    $this->assertFalse($result, 'Domain with hyphens should return FALSE');
  }

  /**
   * Tests that the service works with custom configuration.
   *
   * @covers ::__construct
   * @covers ::match
   */
  public function testCustomConfiguration(): void {
    // Create a custom configuration with test spam sites.
    $customConfig = [
      'sites' => [
        'test-spam.com',
        'malicious.org',
        'phishing.net',
      ],
    ];

    $this->configFactory->getEditable('visitors.spam')
      ->setData($customConfig)
      ->save();

    // Clear the container to get a fresh service instance.
    $this->container->get('kernel')->rebuildContainer();
    $this->spamService = $this->container->get('visitors.spam');

    // Test that the custom configuration is used.
    $result = $this->spamService->match('test-spam.com');
    $this->assertTrue($result, 'Custom spam site should match');

    $result = $this->spamService->match('malicious.org');
    $this->assertTrue($result, 'Custom spam site should match');

    $result = $this->spamService->match('phishing.net');
    $this->assertTrue($result, 'Custom spam site should match');

    // Test that old spam sites no longer match.
    $result = $this->spamService->match('0-0.fr');
    $this->assertFalse($result, 'Old spam site should no longer match');
  }

  /**
   * Tests the service with empty configuration.
   *
   * @covers ::__construct
   * @covers ::match
   */
  public function testEmptyConfiguration(): void {
    // Set empty configuration.
    $this->configFactory->getEditable('visitors.spam')
      ->setData(['sites' => []])
      ->save();

    // Clear the container to get a fresh service instance.
    $this->container->get('kernel')->rebuildContainer();
    $this->spamService = $this->container->get('visitors.spam');

    // Test that no domains match when configuration is empty.
    $result = $this->spamService->match('0-0.fr');
    $this->assertFalse($result, 'No domains should match with empty config');

    $result = $this->spamService->match('google.com');
    $this->assertFalse($result, 'No domains should match with empty config');
  }

  /**
   * Tests the service with missing sites configuration.
   *
   * @covers ::__construct
   * @covers ::match
   */
  public function testMissingSitesConfiguration(): void {
    // Set configuration without the sites key.
    $this->configFactory->getEditable('visitors.spam')
      ->setData([])
      ->save();

    // Clear the container to get a fresh service instance.
    $this->container->get('kernel')->rebuildContainer();
    $this->spamService = $this->container->get('visitors.spam');

    // Test that no domains match when sites configuration is missing.
    $result = $this->spamService->match('0-0.fr');
    $this->assertFalse($result, 'No domains should match with missing sites config');

    $result = $this->spamService->match('google.com');
    $this->assertFalse($result, 'No domains should match with missing sites config');
  }

}
