<?php

use PHPUnit\Framework\TestCase;

/**
 * Test class for get_selected_questions function
 */
class helper_test_by_ai extends TestCase
{
    private $originalDB;

    protected function setUp(): void
    {
        parent::setUp();

        // Store original global DB if it exists
        global $DB;
        $this->originalDB = $DB ?? null;
    }

    protected function tearDown(): void
    {
        // Restore original global DB
        global $DB;
        $DB = $this->originalDB;

        parent::tearDown();
    }

    /**
     * Create a simple database mock that works
     */
    private function createDatabaseMock($inOrEqualReturn = null, $recordsSqlReturn = [])
    {
        global $DB;

        $DB = new class($inOrEqualReturn, $recordsSqlReturn) {
            private $inOrEqualReturn;
            private $recordsSqlReturn;
            public $lastGetInOrEqualCall;
            public $lastGetRecordsSqlCall;

            public function __construct($inOrEqualReturn, $recordsSqlReturn) {
                $this->inOrEqualReturn = $inOrEqualReturn;
                $this->recordsSqlReturn = $recordsSqlReturn;
            }

            public function get_in_or_equal($items) {
                $this->lastGetInOrEqualCall = $items;

                if ($this->inOrEqualReturn !== null) {
                    return $this->inOrEqualReturn;
                }

                // Default behavior
                $placeholders = implode(',', array_fill(0, count($items), '?'));
                return [" IN ($placeholders)", array_map('intval', $items)];
            }

            public function get_records_sql($sql, $params = null) {
                $this->lastGetRecordsSqlCall = ['sql' => $sql, 'params' => $params];
                return $this->recordsSqlReturn;
            }
        };

        return $DB;
    }

    /**
     * Test successful retrieval of selected questions with valid IDs
     */
    public function test_get_selected_questions_with_valid_ids()
    {
        // Arrange
        $fromform = new \stdClass();
        $fromform->selectedquestions = '1,2,3';

        $expectedQuestions = [
            1 => (object)[
                'id' => 1,
                'name' => 'Question 1',
                'questiontext' => 'What is 2+2?',
                'contextid' => 101
            ],
            2 => (object)[
                'id' => 2,
                'name' => 'Question 2',
                'questiontext' => 'What is the capital of France?',
                'contextid' => 102
            ]
        ];

        $mockDB = $this->createDatabaseMock(
            [' IN (?,?,?)', [1, 2, 3]],
            $expectedQuestions
        );

        // Act
        $result = helper_test_by_ai::get_selected_questions($fromform);

        // Assert
        $this->assertIsArray($result);
        $this->assertCount(2, $result);
        $this->assertEquals($expectedQuestions, $result);

        // Verify the database methods were called correctly
        $this->assertEquals(['1', '2', '3'], $mockDB->lastGetInOrEqualCall);
        $this->assertStringContainsString('SELECT q.*, c.contextid', $mockDB->lastGetRecordsSqlCall['sql']);
        $this->assertStringContainsString('FROM {question} q', $mockDB->lastGetRecordsSqlCall['sql']);
        $this->assertEquals([1, 2, 3], $mockDB->lastGetRecordsSqlCall['params']);
    }

    /**
     * Test with single question ID
     */
    public function test_get_selected_questions_with_single_id()
    {
        // Arrange
        $fromform = new \stdClass();
        $fromform->selectedquestions = '5';

        $expectedQuestions = [
            5 => (object)[
                'id' => 5,
                'name' => 'Single Question',
                'questiontext' => 'Test question',
                'contextid' => 105
            ]
        ];

        $mockDB = $this->createDatabaseMock(
            [' IN (?)', [5]],
            $expectedQuestions
        );

        // Act
        $result = helper_test_by_ai::get_selected_questions($fromform);

        // Assert
        $this->assertIsArray($result);
        $this->assertCount(1, $result);
        $this->assertEquals($expectedQuestions, $result);
        $this->assertEquals(['5'], $mockDB->lastGetInOrEqualCall);
    }

    /**
     * Test with empty selectedquestions string
     */
    public function test_get_selected_questions_with_empty_string()
    {
        // Arrange
        $fromform = new \stdClass();
        $fromform->selectedquestions = '';

        $mockDB = $this->createDatabaseMock(
            [' IN (?)', ['']],
            []
        );

        // Act
        $result = helper_test_by_ai::get_selected_questions($fromform);

        // Assert
        $this->assertIsArray($result);
        $this->assertEmpty($result);
        $this->assertEquals([''], $mockDB->lastGetInOrEqualCall);
    }

    /**
     * Test when database returns no results
     */
    public function test_get_selected_questions_no_database_results()
    {
        // Arrange
        $fromform = new \stdClass();
        $fromform->selectedquestions = '999,998';

        $mockDB = $this->createDatabaseMock(
            [' IN (?,?)', [999, 998]],
            [] // No results from database
        );

        // Act
        $result = helper_test_by_ai::get_selected_questions($fromform);

        // Assert
        $this->assertIsArray($result);
        $this->assertEmpty($result);
        $this->assertEquals(['999', '998'], $mockDB->lastGetInOrEqualCall);
    }

    /**
     * Test with whitespace in question IDs (realistic scenario)
     */
    public function test_get_selected_questions_with_whitespace()
    {
        // Arrange
        $fromform = new \stdClass();
        $fromform->selectedquestions = ' 1 , 2 , 3 ';

        $expectedQuestions = [
            1 => (object)['id' => 1, 'contextid' => 101]
        ];

        $mockDB = $this->createDatabaseMock(null, $expectedQuestions);

        // Act
        $result = helper_test_by_ai::get_selected_questions($fromform);

        // Assert
        $this->assertIsArray($result);
        $this->assertEquals($expectedQuestions, $result);
        // Note: explode preserves whitespace, so we expect [' 1 ', ' 2 ', ' 3 ']
        $this->assertEquals([' 1 ', ' 2 ', ' 3 '], $mockDB->lastGetInOrEqualCall);
    }

    /**
     * Test that the correct SQL query structure is generated
     */
    public function test_get_selected_questions_sql_structure()
    {
        // Arrange
        $fromform = new \stdClass();
        $fromform->selectedquestions = '1,2';

        $mockDB = $this->createDatabaseMock();

        // Act
        helper_test_by_ai::get_selected_questions($fromform);

        // Assert - Verify the SQL contains the expected joins and structure
        $sql = $mockDB->lastGetRecordsSqlCall['sql'];

        $this->assertStringContainsString('SELECT q.*, c.contextid', $sql);
        $this->assertStringContainsString('FROM {question} q', $sql);
        $this->assertStringContainsString('JOIN {question_versions} qv ON qv.questionid = q.id', $sql);
        $this->assertStringContainsString('JOIN {question_bank_entries} qbe ON qbe.id = qv.questionbankentryid', $sql);
        $this->assertStringContainsString('JOIN {question_categories} c ON c.id = qbe.questioncategoryid', $sql);
        $this->assertStringContainsString('WHERE q.id', $sql);
        $this->assertStringContainsString('IN (?,?)', $sql);
    }

    /**
     * Test behavior when selectedquestions property is missing
     */
    public function test_get_selected_questions_missing_property()
    {
        // Arrange
        $fromform = new \stdClass();
        // Don't set selectedquestions property

        // This will trigger a PHP notice/warning, but we'll suppress it for the test
        $originalErrorReporting = error_reporting();
        error_reporting(E_ERROR); // Suppress notices and warnings

        try {
            // Act
            $result = helper_test_by_ai::get_selected_questions($fromform);

            // Assert
            $this->assertIsArray($result);
            $this->assertEmpty($result);
        } finally {
            // Restore error reporting
            error_reporting($originalErrorReporting);
        }
    }

    /**
     * Test return type is always array for various inputs
     */
    public function test_get_selected_questions_always_returns_array()
    {
        $testCases = [
            ['selectedquestions' => '1,2,3'],
            ['selectedquestions' => ''],
            ['selectedquestions' => '0'],
            ['selectedquestions' => 'invalid'],
        ];

        foreach ($testCases as $index => $testData) {
            $fromform = (object) $testData;

            // Create fresh mock for each test
            $this->createDatabaseMock(null, $index === 0 ? ['some_data'] : []);

            $result = helper_test_by_ai::get_selected_questions($fromform);
            $this->assertIsArray($result, "Failed for test case index: $index");
        }
    }

    /**
     * Test with numeric question IDs to ensure proper type handling
     */
    public function test_get_selected_questions_with_numeric_ids()
    {
        // Arrange
        $fromform = new \stdClass();
        $fromform->selectedquestions = '123,456,789';

        $expectedQuestions = [
            123 => (object)['id' => 123, 'contextid' => 1],
            456 => (object)['id' => 456, 'contextid' => 2],
            789 => (object)['id' => 789, 'contextid' => 3],
        ];

        $mockDB = $this->createDatabaseMock(null, $expectedQuestions);

        // Act
        $result = helper_test_by_ai::get_selected_questions($fromform);

        // Assert
        $this->assertIsArray($result);
        $this->assertCount(3, $result);
        $this->assertEquals($expectedQuestions, $result);

        // Verify that string IDs were passed to get_in_or_equal (before conversion)
        $this->assertEquals(['123', '456', '789'], $mockDB->lastGetInOrEqualCall);

        // Verify that converted integer IDs were passed to get_records_sql
        $this->assertEquals([123, 456, 789], $mockDB->lastGetRecordsSqlCall['params']);
    }
}