<?php

declare(strict_types=1);

namespace Tests\Support;

use PHPUnit\Framework\TestCase as PHPUnitTestCase;

abstract class QdbTestCase extends PHPUnitTestCase
{
    protected static ApiClient $api;

    protected ?FixtureIds $fixtureIds = null;

    public static function setUpBeforeClass(): void
    {
        parent::setUpBeforeClass();
        self::$api = new ApiClient(dirname(__DIR__, 2) . '/api/index.php');
    }

    protected function setUp(): void
    {
        parent::setUp();
        TestFilesystem::resetUploads();
        $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
        $this->bootstrapDatabase();
    }

    protected function bootstrapDatabase(): void
    {
        if (defined('QDB_PATH') && is_file(QDB_PATH)) {
            @unlink(QDB_PATH);
        }
        if (defined('QDB_LOCK') && is_file(QDB_LOCK)) {
            @unlink(QDB_LOCK);
        }

        [$pdo, $tmpDb, $lockFp] = qdb_open(true);
        $this->fixtureIds = DatabaseSeeder::seed($pdo);
        qdb_save($tmpDb, $lockFp);
    }

    protected function api(): ApiClient
    {
        return self::$api;
    }

    protected function fixture(): FixtureIds
    {
        if ($this->fixtureIds === null) {
            $this->fail('Fixture not seeded');
        }
        return $this->fixtureIds;
    }

    protected function adminToken(): string
    {
        return $this->api()->loginWebAndGetToken(
            DatabaseSeeder::ADMIN_USERNAME,
            DatabaseSeeder::PASSWORD
        )['token'];
    }

    protected function supervisorToken(): string
    {
        return $this->api()->loginWebAndGetToken(
            DatabaseSeeder::SUPERVISOR_USERNAME,
            DatabaseSeeder::PASSWORD
        )['token'];
    }

    /**
     * @return array{token: string, data: array<string, mixed>}
     */
    protected function coachMobileLogin(): array
    {
        return $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::COACH_USERNAME,
            DatabaseSeeder::PASSWORD
        );
    }

    protected function assertApiOk(array $response, string $message = ''): void
    {
        $this->assertTrue($response['ok'] ?? false, $message . ' ' . json_encode($response['error'] ?? []));
    }

    protected function assertApiError(array $response, string $code, ?int $status = null): void
    {
        $this->assertFalse($response['ok'] ?? true);
        $this->assertSame($code, $response['error']['code'] ?? null);
        if ($status !== null) {
            $this->assertSame($status, $response['status']);
        }
    }

    protected function assertRawOk(array $response, string $contentTypePrefix): void
    {
        $this->assertTrue($response['ok'] ?? false);
        $this->assertTrue($response['raw'] ?? false);
        $this->assertStringStartsWith($contentTypePrefix, $response['headers']['Content-Type'] ?? '');
        $this->assertNotSame('', $response['body'] ?? '');
    }

    /**
     * @return array<string, mixed> API response from app submit
     */
    protected function submitFixtureQuestionnaire(): array
    {
        $login = $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::COACH_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $f = $this->fixture();
        $now = time();

        return $this->submitQuestionnaireAs(
            $login['token'],
            $f->questionnaireId,
            $f->clientCode,
            [
                ['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
            ],
            $now - 60,
            $now
        );
    }

    /**
     * @param list<array<string, mixed>> $answers
     * @return array<string, mixed>
     */
    protected function submitQuestionnaireAs(
        string $token,
        string $questionnaireId,
        string $clientCode,
        array $answers,
        ?int $startedAt = null,
        ?int $completedAt = null,
        ?int $structureRevision = null,
    ): array {
        $now = time();
        $startedAt ??= $now - 60;
        $completedAt ??= $now;

        $payload = [
            'questionnaireID' => $questionnaireId,
            'clientCode'      => $clientCode,
            'answers'         => $answers,
            'startedAt'       => $startedAt,
            'completedAt'     => $completedAt,
        ];
        if ($structureRevision !== null) {
            $payload['structureRevision'] = $structureRevision;
        }

        return $this->api()->encryptedPost($token, 'app_questionnaires', $payload);
    }

    protected function submissionVersionCount(string $clientCode, string $questionnaireId): int
    {
        [$pdo, $tmpDb, $lockFp] = qdb_open(false);
        $stmt = $pdo->prepare(
            'SELECT COUNT(*) FROM questionnaire_submission WHERE clientCode = :cc AND questionnaireID = :qn'
        );
        $stmt->execute([':cc' => $clientCode, ':qn' => $questionnaireId]);
        $count = (int)$stmt->fetchColumn();
        qdb_discard($tmpDb, $lockFp);

        return $count;
    }
}
