<?php

declare(strict_types=1);

namespace OCA\Mitgliederverwaltung\Tests\DatabasePortability;

use OCA\Mitgliederverwaltung\Db\PlatformHelper;
use OCP\IDBConnection;
use PHPUnit\Framework\TestCase;

/**
 * Integration test suite for database portability.
 *
 * These tests verify that the SQL expressions generated by PlatformHelper
 * are syntactically valid and semantically correct for each database backend.
 * They run against a real Nextcloud database when available (set DB_PLATFORM
 * env var), or fall back to expression-level validation.
 *
 * To run against a live database:
 *   DB_PLATFORM=mysql   phpunit tests/DatabasePortability/
 *   DB_PLATFORM=postgres phpunit tests/DatabasePortability/
 *   DB_PLATFORM=sqlite  phpunit tests/DatabasePortability/
 *
 * Part of Issue #192.
 */
class DatabasePortabilityTest extends TestCase {

    /**
     * Verify that PlatformHelper generates structurally valid SQL
     * for all three backends — each expression must be a non-empty string
     * containing the referenced column.
     *
     * @dataProvider platformAndMethodProvider
     */
    public function testExpressionStructure(string $platform, string $method, string $col): void {
        $db = $this->createMock(IDBConnection::class);
        $db->method('getDatabaseProvider')->willReturn($platform);

        $expr = PlatformHelper::$method($db, $col);

        $this->assertIsString($expr);
        $this->assertNotEmpty($expr);
        $this->assertStringContainsString($col, $expr,
            "Expression for $method on $platform must reference the column");
    }

    public static function platformAndMethodProvider(): array {
        $platforms = [
            IDBConnection::PLATFORM_MYSQL,
            IDBConnection::PLATFORM_POSTGRES,
            IDBConnection::PLATFORM_SQLITE,
        ];
        $methods = [
            ['getYearDiffExpression', 'm.geburtsdatum'],
            ['getMonthExpression', 'm.geburtsdatum'],
            ['getYearExpression', 'l.startdatum'],
        ];

        $cases = [];
        foreach ($platforms as $p) {
            foreach ($methods as [$m, $c]) {
                $cases["$p/$m"] = [$p, $m, $c];
            }
        }
        return $cases;
    }

    /**
     * Verify that MySQL expressions use the expected MySQL-specific functions.
     */
    public function testMySQLExpressionsUseCorrectFunctions(): void {
        $db = $this->createMock(IDBConnection::class);
        $db->method('getDatabaseProvider')->willReturn(IDBConnection::PLATFORM_MYSQL);

        $this->assertStringContainsString('TIMESTAMPDIFF',
            PlatformHelper::getYearDiffExpression($db, 'col'));
        $this->assertStringContainsString('MONTH(',
            PlatformHelper::getMonthExpression($db, 'col'));
        $this->assertStringContainsString('YEAR(',
            PlatformHelper::getYearExpression($db, 'col'));
    }

    /**
     * Verify that PostgreSQL expressions use EXTRACT and AGE.
     */
    public function testPostgresExpressionsUseCorrectFunctions(): void {
        $db = $this->createMock(IDBConnection::class);
        $db->method('getDatabaseProvider')->willReturn(IDBConnection::PLATFORM_POSTGRES);

        $yearDiff = PlatformHelper::getYearDiffExpression($db, 'col');
        $this->assertStringContainsString('EXTRACT', $yearDiff);
        $this->assertStringContainsString('AGE', $yearDiff);

        $this->assertStringContainsString('EXTRACT(MONTH',
            PlatformHelper::getMonthExpression($db, 'col'));
        $this->assertStringContainsString('EXTRACT(YEAR',
            PlatformHelper::getYearExpression($db, 'col'));
    }

    /**
     * Verify that SQLite expressions use strftime.
     */
    public function testSQLiteExpressionsUseCorrectFunctions(): void {
        $db = $this->createMock(IDBConnection::class);
        $db->method('getDatabaseProvider')->willReturn(IDBConnection::PLATFORM_SQLITE);

        $yearDiff = PlatformHelper::getYearDiffExpression($db, 'col');
        $this->assertStringContainsString('strftime', $yearDiff);
        // SQLite year diff includes birthday correction logic
        $this->assertStringContainsString('CASE WHEN', $yearDiff);

        $this->assertStringContainsString('strftime',
            PlatformHelper::getMonthExpression($db, 'col'));
        $this->assertStringContainsString('strftime',
            PlatformHelper::getYearExpression($db, 'col'));
    }

    /**
     * Verify that no platform expression contains obvious SQL injection vectors.
     * Column names should appear verbatim, and no user-controllable content
     * should be interpolated.
     */
    public function testNoInjectionVectorsInExpressions(): void {
        $platforms = [
            IDBConnection::PLATFORM_MYSQL,
            IDBConnection::PLATFORM_POSTGRES,
            IDBConnection::PLATFORM_SQLITE,
        ];

        foreach ($platforms as $platform) {
            $db = $this->createMock(IDBConnection::class);
            $db->method('getDatabaseProvider')->willReturn($platform);

            // Use a safe column reference
            $col = 'm.test_col';
            $methods = ['getYearDiffExpression', 'getMonthExpression', 'getYearExpression'];

            foreach ($methods as $method) {
                $expr = PlatformHelper::$method($db, $col);

                // Must not contain any parameter placeholders (? or :param)
                // because these are raw expressions, not parameterized queries
                $this->assertStringNotContainsString('?', $expr,
                    "Expression for $method on $platform must not contain ? placeholders");

                // The column reference must appear exactly as passed
                $this->assertStringContainsString($col, $expr,
                    "Expression for $method on $platform must reference column verbatim");
            }
        }
    }
}
