<?php

declare(strict_types=1);

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

final class ApiLogTest extends TestCase
{
    private string $logDir;

    protected function setUp(): void
    {
        require_once dirname(__DIR__, 2) . '/lib/api_log.php';
        $this->logDir = $_ENV['QDB_API_LOG_DIR'] ?? (QDB_TEST_UPLOADS . '/logs/api');
        if (!is_dir($this->logDir)) {
            mkdir($this->logDir, 0775, true);
        }
        $this->clearLogFiles();
        unset($GLOBALS['qdb_api_log_ctx']);
    }

    protected function tearDown(): void
    {
        $this->clearLogFiles();
        unset($GLOBALS['qdb_api_log_ctx']);
    }

    public function testClassifyActivity(): void
    {
        $this->assertNull(qdb_api_log_classify_activity('OPTIONS', 'web', 'users'));
        $this->assertSame('web_change', qdb_api_log_classify_activity('POST', 'web', 'users'));
        $this->assertSame('app_change', qdb_api_log_classify_activity('POST', '', 'app_questionnaires'));
        $this->assertSame('app_sync', qdb_api_log_classify_activity('GET', '', 'app_questionnaires'));
        $this->assertSame('web_export', qdb_api_log_classify_activity('GET', 'web', 'export'));
        $this->assertSame('web_change', qdb_api_log_classify_activity('POST', 'web', 'backup'));
        $this->assertTrue(qdb_api_log_is_website_download('backup'));
    }

    public function testRedactSensitiveFields(): void
    {
        $redacted = qdb_api_log_redact_value([
            'username' => 'coach1',
            'password' => 'secret',
            'encrypted' => str_repeat('x', 40),
        ]);
        $this->assertSame('coach1', $redacted['username']);
        $this->assertSame('[REDACTED]', $redacted['password']);
        $this->assertSame('[ENCRYPTED:40 chars]', $redacted['encrypted']);
    }

    public function testRedactQueryString(): void
    {
        $qs = qdb_api_log_redact_query_string('questionnaireID=qn1&token=abc&password=pw');
        parse_str((string)$qs, $params);
        $this->assertSame('qn1', $params['questionnaireID'] ?? null);
        $this->assertSame('[REDACTED]', $params['token'] ?? null);
        $this->assertSame('[REDACTED]', $params['password'] ?? null);
    }

    public function testTokenHintMasksMiddle(): void
    {
        $hint = qdb_api_log_token_hint('0123456789abcdef');
        $this->assertStringContainsString('…', $hint);
        $this->assertStringNotContainsString('56789', $hint);
    }

    public function testClientIpUsesForwardedFor(): void
    {
        $_SERVER['HTTP_X_FORWARDED_FOR'] = '203.0.113.50, 198.51.100.1';
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
        $this->assertSame('203.0.113.50', qdb_api_log_client_ip());
        unset($_SERVER['HTTP_X_FORWARDED_FOR']);
    }

    public function testBeginFinishWritesWebChangeEntry(): void
    {
        $_SERVER['HTTP_X_QDB_CLIENT'] = 'web';
        $_SERVER['REQUEST_URI'] = '/api/users';
        $_SERVER['QUERY_STRING'] = '';
        qdb_test_set_request_body(json_encode(['username' => 'new_coach'], JSON_THROW_ON_ERROR));
        http_response_code(200);

        qdb_api_log_begin('POST', 'users');
        qdb_api_log_finish();

        $date = date('Y-m-d');
        $read = qdb_api_log_read_entries($date, 'web_change', 10);
        $entries = $read['entries'];
        $this->assertNotEmpty($entries);
        $last = $entries[0];
        $this->assertSame('web_change', $last['activity']);
        $this->assertSame('POST', $last['method']);
        $this->assertSame('users', $last['route']);
        $changes = $last['changes'] ?? [];
        $fields = array_column($changes, 'field');
        $this->assertContains('username', $fields);

        qdb_test_reset_http_state();
    }

    private function clearLogFiles(): void
    {
        foreach (glob($this->logDir . '/api-*.log') ?: [] as $path) {
            @unlink($path);
        }
    }
}
