<?php

declare(strict_types=1);

namespace Drupal\Tests\visitors\Kernel\Plugin\views\sort;

use Drupal\Core\Database\Connection;
use Drupal\KernelTests\KernelTestBase;
use Drupal\views\ResultRow;
use Drupal\views\ViewExecutable;
use Drupal\visitors\Plugin\views\sort\NumberRange;

/**
 * Kernel tests for NumberRange views sort plugin.
 *
 * @group visitors
 * @coversDefaultClass \Drupal\visitors\Plugin\views\sort\NumberRange
 */
class NumberRangeTest extends KernelTestBase {

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

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected Connection $database;

  /**
   * The sort plugin instance.
   *
   * @var \Drupal\visitors\Plugin\views\sort\NumberRange
   */
  protected NumberRange $sort;

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

    $this->installEntitySchema('user');
    $this->installSchema('system', ['sequences']);
    $this->installSchema('visitors', ['visitors_visit', 'visitors_event']);
    $this->installConfig(['user', 'system', 'views']);

    $this->database = $this->container->get('database');

    // Insert test data.
    $this->insertTestData();

    // Create the sort plugin instance.
    $configuration = [
      'table' => 'visitors_visit',
      'field' => 'total_page_views',
    ];
    $plugin_id = 'visitors_number_range';
    $plugin_definition = [];
    $this->sort = NumberRange::create($this->container, $configuration, $plugin_id, $plugin_definition);

    // Set up the sort plugin with a mock view.
    $view = $this->createMock(ViewExecutable::class);
    $this->sort->view = $view;
    $this->sort->tableAlias = 'vv';
    $this->sort->realField = 'total_page_views';

    // Set default options to prevent undefined array key warnings.
    $this->sort->options = [
      'ranges' => "0\n1\n2\n3\n4\n5\n6-7\n8-10\n11-14\n15-20\n21+",
      'order' => 'ASC',
    ];
  }

  /**
   * Inserts test data into the visitors_visit table.
   */
  protected function insertTestData(): void {
    $test_data = [
      [
        'id' => 1,
        'visitor_id' => 'visitor1',
        'uid' => 0,
        'entry_time' => time(),
        'exit_time' => time() + 300,
        'location_ip' => '127.0.0.1',
        'config_id' => 'config1',
        'referer_type' => 'direct',
        'referer_url' => '',
        'referer_name' => NULL,
        'referer_keyword' => NULL,
        'total_page_views' => 1,
        'total_events' => 2,
        'total_time' => 300,
        'total_visits' => 1,
      ],
      [
        'id' => 2,
        'visitor_id' => 'visitor2',
        'uid' => 0,
        'entry_time' => time() - 3600,
        'exit_time' => time() - 3300,
        'location_ip' => '127.0.0.2',
        'config_id' => 'config2',
        'referer_type' => 'website',
        'referer_url' => 'http://google.com/search?q=test',
        'referer_name' => 'Google',
        'referer_keyword' => 'test',
        'total_page_views' => 5,
        'total_events' => 8,
        'total_time' => 1800,
        'total_visits' => 3,
      ],
      [
        'id' => 3,
        'visitor_id' => 'visitor3',
        'uid' => 0,
        'entry_time' => time() - 7200,
        'exit_time' => time() - 6900,
        'location_ip' => '127.0.0.3',
        'config_id' => 'config3',
        'referer_type' => 'search_engine',
        'referer_url' => 'http://bing.com/search?q=test',
        'referer_name' => 'Bing',
        'referer_keyword' => 'test',
        'total_page_views' => 12,
        'total_events' => 15,
        'total_time' => 3600,
        'total_visits' => 5,
      ],
      [
        'id' => 4,
        'visitor_id' => 'visitor4',
        'uid' => 0,
        'entry_time' => time() - 10800,
        'exit_time' => time() - 10500,
        'location_ip' => '127.0.0.4',
        'config_id' => 'config4',
        'referer_type' => 'social_network',
        'referer_url' => 'http://facebook.com/share',
        'referer_name' => 'Facebook',
        'referer_keyword' => NULL,
        'total_page_views' => 25,
        'total_events' => 30,
        'total_time' => 7200,
        'total_visits' => 10,
      ],
      [
        'id' => 5,
        'visitor_id' => 'visitor5',
        'uid' => 0,
        'entry_time' => time() - 14400,
        'exit_time' => time() - 14100,
        'location_ip' => '127.0.0.5',
        'config_id' => 'config5',
        'referer_type' => 'direct',
        'referer_url' => '',
        'referer_name' => NULL,
        'referer_keyword' => NULL,
        'total_page_views' => 0,
        'total_events' => 1,
        'total_time' => 60,
        'total_visits' => 1,
      ],
    ];

    foreach ($test_data as $data) {
      $this->database->insert('visitors_visit')->fields($data)->execute();
    }
  }

  /**
   * Tests the getTotalRanges method with various range configurations.
   *
   * @covers ::getTotalRanges
   */
  public function testGetTotalRanges(): void {
    // Test with simple ranges.
    $this->sort->options['ranges'] = "0\n1\n2\n3";
    $this->assertEquals(4, $this->sort->getTotalRanges());

    // Test with range notation.
    $this->sort->options['ranges'] = "0\n1-2\n3-5\n6-10\n11+";
    $this->assertEquals(5, $this->sort->getTotalRanges());

    // Test with empty ranges.
    $this->sort->options['ranges'] = '';
    $this->assertEquals(0, $this->sort->getTotalRanges());

    // Test with whitespace-only ranges.
    $this->sort->options['ranges'] = "  \n  \n  ";
    $this->assertEquals(0, $this->sort->getTotalRanges());
  }

  /**
   * Tests the getTotalRanges method with edge cases.
   *
   * @covers ::getTotalRanges
   */
  public function testGetTotalRangesEdgeCases(): void {
    // Test with empty ranges.
    $this->sort->options['ranges'] = '';
    $this->assertEquals(0, $this->sort->getTotalRanges());

    // Test with whitespace-only ranges.
    $this->sort->options['ranges'] = "  \n  \n  ";
    $this->assertEquals(0, $this->sort->getTotalRanges());

    // Test with single range.
    $this->sort->options['ranges'] = '0';
    $this->assertEquals(1, $this->sort->getTotalRanges());

    // Test with single range with whitespace.
    $this->sort->options['ranges'] = '  0  ';
    $this->assertEquals(1, $this->sort->getTotalRanges());
  }

  /**
   * Tests the postExecute method with various data.
   *
   * @covers ::postExecute
   */
  public function testPostExecute(): void {
    $this->sort->options['ranges'] = "0\n1\n2\n3\n4\n5\n6-7\n8-10\n11-14\n15-20\n21+";

    $values = [
      new ResultRow(['vv_total_page_views__range' => 1]),
      new ResultRow(['vv_total_page_views__range' => 2]),
      new ResultRow(['vv_total_page_views__range' => 3]),
    ];

    $this->sort->postExecute($values);

    // SequenceService::integer fills in missing values, so the result will be
    // larger.
    $this->assertGreaterThan(3, count($values));
  }

  /**
   * Tests the postExecute method with empty values.
   *
   * @covers ::postExecute
   */
  public function testPostExecuteWithEmptyValues(): void {
    $this->sort->options['ranges'] = "0\n1\n2";

    $values = [];

    $this->sort->postExecute($values);

    $this->assertIsArray($values);
    $this->assertCount(0, $values);
  }

  /**
   * Tests the postExecute method with single value.
   *
   * @covers ::postExecute
   */
  public function testPostExecuteWithSingleValue(): void {
    $this->sort->options['ranges'] = "0\n1\n2";

    $values = [
      new ResultRow(['vv_total_page_views__range' => 1]),
    ];

    $this->sort->postExecute($values);

    // SequenceService::integer fills in missing values 0 and 2.
    $this->assertGreaterThan(1, count($values));
  }

  /**
   * Tests the postExecute method with large number of ranges.
   *
   * @covers ::postExecute
   */
  public function testPostExecuteWithLargeNumberOfRanges(): void {
    $ranges = [];
    for ($i = 0; $i < 100; $i++) {
      $ranges[] = $i;
    }
    $this->sort->options['ranges'] = implode("\n", $ranges);

    $values = [
      new ResultRow(['vv_total_page_views__range' => 50]),
      new ResultRow(['vv_total_page_views__range' => 75]),
    ];

    $this->sort->postExecute($values);

    // SequenceService::integer fills in missing values from 0 to 99.
    $this->assertGreaterThan(2, count($values));
  }

  /**
   * Tests the postExecute method with non-sequential values.
   *
   * @covers ::postExecute
   */
  public function testPostExecuteWithNonSequentialValues(): void {
    $this->sort->options['ranges'] = "0\n1\n2\n3\n4\n5";

    $values = [
      new ResultRow(['vv_total_page_views__range' => 5]),
      new ResultRow(['vv_total_page_views__range' => 1]),
      new ResultRow(['vv_total_page_views__range' => 3]),
    ];

    $this->sort->postExecute($values);

    // SequenceService::integer fills in missing values 0, 2, and 4.
    $this->assertGreaterThan(3, count($values));
  }

  /**
   * Tests the postExecute method with out-of-range values.
   *
   * @covers ::postExecute
   */
  public function testPostExecuteWithOutOfRangeValues(): void {
    $this->sort->options['ranges'] = "0\n1\n2";

    $values = [
      new ResultRow(['vv_total_page_views__range' => 999]),
      new ResultRow(['vv_total_page_views__range' => 0]),
    ];

    $this->sort->postExecute($values);

    // SequenceService::integer fills in missing values 1 and 2.
    $this->assertGreaterThan(2, count($values));
  }

  /**
   * Tests the postExecute method with mixed data types.
   *
   * @covers ::postExecute
   */
  public function testPostExecuteWithMixedDataTypes(): void {
    $this->sort->options['ranges'] = "0\n1\n2";

    $values = [
      new ResultRow(['vv_total_page_views__range' => '1']),
      new ResultRow(['vv_total_page_views__range' => 2]),
      new ResultRow(['vv_total_page_views__range' => '0']),
    ];

    $this->sort->postExecute($values);

    // SequenceService::integer fills in missing values,
    // but since we have all 3, it should be 3.
    $this->assertGreaterThanOrEqual(3, count($values));
  }

  /**
   * Tests the postExecute method with missing field values.
   *
   * @covers ::postExecute
   */
  public function testPostExecuteWithMissingFieldValues(): void {
    $this->sort->options['ranges'] = "0\n1\n2";

    $values = [
      new ResultRow(['other_field' => 'value', 'vv_total_page_views__range' => NULL]),
      new ResultRow(['vv_total_page_views__range' => 1]),
    ];

    $this->sort->postExecute($values);

    // SequenceService::integer fills in missing values 0 and 2.
    $this->assertGreaterThan(2, count($values));
  }

  /**
   * Tests the postExecute method with null values.
   *
   * @covers ::postExecute
   */
  public function testPostExecuteWithNullValues(): void {
    $this->sort->options['ranges'] = "0\n1\n2";

    $values = [
      new ResultRow(['vv_total_page_views__range' => NULL]),
      new ResultRow(['vv_total_page_views__range' => 1]),
    ];

    $this->sort->postExecute($values);

    // SequenceService::integer fills in missing values 0 and 2.
    $this->assertGreaterThan(2, count($values));
  }

  /**
   * Tests the postExecute method with complex range configurations.
   *
   * @covers ::postExecute
   */
  public function testPostExecuteWithComplexRangeConfigurations(): void {
    $complex_ranges = [
      "0",
      "1-2",
      "3-5",
      "6-10",
      "11-15",
      "16-20",
      "21-25",
      "26-30",
      "31-40",
      "41-50",
      "51+",
    ];
    $this->sort->options['ranges'] = implode("\n", $complex_ranges);

    $values = [
      new ResultRow(['vv_total_page_views__range' => 5]),
      new ResultRow(['vv_total_page_views__range' => 15]),
      new ResultRow(['vv_total_page_views__range' => 25]),
      new ResultRow(['vv_total_page_views__range' => 35]),
      new ResultRow(['vv_total_page_views__range' => 45]),
      new ResultRow(['vv_total_page_views__range' => 55]),
    ];

    $this->sort->postExecute($values);

    // SequenceService::integer fills in missing values for all ranges.
    $this->assertGreaterThan(6, count($values));
  }

  /**
   * Tests that the query method can be called without errors.
   *
   * @covers ::query
   */
  public function testQueryMethodCanBeCalled(): void {
    // Set up a mock query to avoid database errors in kernel tests.
    $mock_query = $this->createMock('Drupal\views\Plugin\views\query\Sql');
    $this->sort->query = $mock_query;

    // Set up the query fields to simulate that our target field doesn't exist.
    $mock_query->fields = [
      [
        'alias' => 'other_field',
        'field' => 'some_other_field',
      ],
    ];

    // The method should not throw any errors.
    $this->sort->query();

    // This test primarily ensures the method can be called without crashing.
    $this->assertTrue(TRUE);
  }

}
