<?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\SocialNetworksService;

/**
 * Tests the SocialNetworksService class.
 *
 * @group visitors
 * @coversDefaultClass \Drupal\visitors\Service\SocialNetworksService
 */
class SocialNetworksServiceTest 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 social networks service.
   *
   * @var \Drupal\visitors\Service\SocialNetworksService
   */
  private SocialNetworksService $socialNetworksService;

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

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

    $this->socialNetworksService = new SocialNetworksService($this->configFactory);
  }

  /**
   * Tests successful social network hostname matching.
   *
   * @covers ::match
   */
  public function testMatchSuccess(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'Facebook',
          'hosts' => ['facebook.com', 'fb.me', 'm.facebook.com'],
        ],
        [
          'label' => 'Twitter',
          'hosts' => ['twitter.com', 't.co', 'x.com'],
        ],
        [
          'label' => 'LinkedIn',
          'hosts' => ['linkedin.com', 'lnkd.in'],
        ],
      ],
    ];

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

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

    $result = $this->socialNetworksService->match('facebook.com');
    $this->assertEquals('Facebook', $result);

    $result = $this->socialNetworksService->match('fb.me');
    $this->assertEquals('Facebook', $result);

    $result = $this->socialNetworksService->match('twitter.com');
    $this->assertEquals('Twitter', $result);

    $result = $this->socialNetworksService->match('x.com');
    $this->assertEquals('Twitter', $result);

    $result = $this->socialNetworksService->match('linkedin.com');
    $this->assertEquals('LinkedIn', $result);
  }

  /**
   * Tests failed social network hostname matching.
   *
   * @covers ::match
   */
  public function testMatchFailure(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'Facebook',
          'hosts' => ['facebook.com'],
        ],
      ],
    ];

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

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

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

    $result = $this->socialNetworksService->match('instagram.com');
    $this->assertNull($result);
  }

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

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

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

    $result = $this->socialNetworksService->match('FACEBOOK.COM');
    $this->assertEquals('Facebook', $result);

    $result = $this->socialNetworksService->match('FaceBook.Com');
    $this->assertEquals('Facebook', $result);
  }

  /**
   * Tests hostname matching with whitespace.
   *
   * @covers ::match
   */
  public function testMatchWithWhitespace(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'Facebook',
          'hosts' => ['facebook.com'],
        ],
      ],
    ];

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

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

    $result = $this->socialNetworksService->match('  facebook.com  ');
    $this->assertEquals('Facebook', $result);

    $result = $this->socialNetworksService->match('facebook.com ');
    $this->assertEquals('Facebook', $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.social_networks')
      ->willReturn($this->config);

    $result = $this->socialNetworksService->match('facebook.com');
    $this->assertNull($result);
  }

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

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

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

    // First call should load config.
    $result1 = $this->socialNetworksService->match('facebook.com');
    $this->assertEquals('Facebook', $result1);

    // Second call should use cached config.
    $result2 = $this->socialNetworksService->match('facebook.com');
    $this->assertEquals('Facebook', $result2);
  }

  /**
   * Tests complex social network configurations.
   *
   * @covers ::match
   */
  public function testComplexConfigurations(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'Mastodon',
          'hosts' => [
            'mastodon.social',
            'mastodon.cloud',
            'mastodon.technology',
            'mastodon.xyz',
          ],
        ],
        [
          'label' => 'Pinterest',
          'hosts' => [
            'pinterest.com',
            'pinterest.ca',
            'pinterest.co.uk',
            'pinterest.de',
          ],
        ],
      ],
    ];

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

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

    // Test Mastodon variations.
    $result = $this->socialNetworksService->match('mastodon.social');
    $this->assertEquals('Mastodon', $result);

    $result = $this->socialNetworksService->match('mastodon.cloud');
    $this->assertEquals('Mastodon', $result);

    $result = $this->socialNetworksService->match('mastodon.xyz');
    $this->assertEquals('Mastodon', $result);

    // Test Pinterest variations.
    $result = $this->socialNetworksService->match('pinterest.com');
    $this->assertEquals('Pinterest', $result);

    $result = $this->socialNetworksService->match('pinterest.co.uk');
    $this->assertEquals('Pinterest', $result);

    $result = $this->socialNetworksService->match('pinterest.de');
    $this->assertEquals('Pinterest', $result);
  }

  /**
   * Tests empty hostname handling.
   *
   * @covers ::match
   */
  public function testEmptyHostname(): void {
    $result = $this->socialNetworksService->match('');
    $this->assertNull($result);

    $result = $this->socialNetworksService->match('   ');
    $this->assertNull($result);
  }

  /**
   * Tests malformed configuration handling.
   *
   * @covers ::match
   */
  public function testMalformedConfiguration(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'Facebook',
          // Missing hosts key.
        ],
        [
          // Missing label key.
          'hosts' => ['twitter.com'],
        ],
        [
          'label' => 'LinkedIn',
        // Hosts should be an array.
          'hosts' => 'not-an-array',
        ],
      ],
    ];

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

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

    // All should return null due to malformed config.
    $result = $this->socialNetworksService->match('facebook.com');
    $this->assertNull($result);

    $result = $this->socialNetworksService->match('twitter.com');
    $this->assertNull($result);

    $result = $this->socialNetworksService->match('linkedin.com');
    $this->assertNull($result);
  }

  /**
   * Tests the getConfig method directly.
   *
   * @covers ::getConfig
   */
  public function testGetConfig(): void {
    $configData = [
      'sites' => [
        [
          'label' => 'Facebook',
          'hosts' => ['facebook.com', 'fb.me'],
        ],
        [
          'label' => 'Twitter',
          'hosts' => ['twitter.com', 't.co'],
        ],
      ],
    ];

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

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

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

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

    $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.social_networks')
      ->willReturn($this->config);

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

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

    $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.social_networks')
      ->willReturn($this->config);

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

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

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

}
