<?php

declare(strict_types=1);

namespace Tests\Integration;

use Tests\Support\DatabaseSeeder;
use Tests\Support\QdbTestCase;

final class AppSubmitExtendedTest extends QdbTestCase
{
    public function testResubmitClearsOmittedOptionalAnswers(): void
    {
        $login = $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::COACH_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $f = $this->fixture();

        $this->assertApiOk($this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
            ['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
            ['questionID' => $f->freeTextShortId, 'freeTextValue' => 'Old notes'],
            ['questionID' => $f->glassSymptomKey, 'freeTextValue' => 'moderate'],
            ['questionID' => 'fatigue', 'freeTextValue' => 'severe'],
        ]));

        $res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
            ['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
        ]);
        $this->assertApiOk($res);

        [$pdo, $tmpDb, $lockFp] = qdb_open(false);

        $ft = $pdo->prepare(
            'SELECT freeTextValue FROM client_answer WHERE clientCode = :cc AND questionID = :qid'
        );
        $ft->execute([':cc' => $f->clientCode, ':qid' => $f->freeTextQuestionId]);
        $this->assertFalse($ft->fetchColumn());

        $glass = $pdo->prepare(
            'SELECT freeTextValue FROM client_answer WHERE clientCode = :cc AND questionID = :qid'
        );
        $glass->execute([':cc' => $f->clientCode, ':qid' => $f->glassQuestionId]);
        $this->assertFalse($glass->fetchColumn());

        $sub = $pdo->prepare(
            'SELECT submissionID FROM questionnaire_submission
             WHERE clientCode = :cc AND questionnaireID = :qn ORDER BY version DESC LIMIT 1'
        );
        $sub->execute([':cc' => $f->clientCode, ':qn' => $f->questionnaireId]);
        $submissionId = (string)$sub->fetchColumn();

        $archived = $pdo->prepare(
            'SELECT freeTextValue FROM client_answer_submission
             WHERE submissionID = :sid AND questionID = :qid'
        );
        $archived->execute([':sid' => $submissionId, ':qid' => $f->freeTextQuestionId]);
        $this->assertFalse($archived->fetchColumn());

        $archivedGlass = $pdo->prepare(
            'SELECT freeTextValue FROM client_answer_submission
             WHERE submissionID = :sid AND questionID = :qid'
        );
        $archivedGlass->execute([':sid' => $submissionId, ':qid' => $f->glassQuestionId]);
        $this->assertFalse($archivedGlass->fetchColumn());

        qdb_discard($tmpDb, $lockFp);
    }

    public function testResubmitReplacesGlassSymptomsInsteadOfMerging(): void
    {
        $login = $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::COACH_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $f = $this->fixture();

        $this->assertApiOk($this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
            ['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
            ['questionID' => $f->glassSymptomKey, 'freeTextValue' => 'moderate'],
            ['questionID' => 'fatigue', 'freeTextValue' => 'severe'],
        ]));

        $res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
            ['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
            ['questionID' => $f->glassSymptomKey, 'freeTextValue' => 'mild'],
        ]);
        $this->assertApiOk($res);

        [$pdo, $tmpDb, $lockFp] = qdb_open(false);
        $glass = $pdo->prepare(
            'SELECT freeTextValue FROM client_answer WHERE clientCode = :cc AND questionID = :qid'
        );
        $glass->execute([':cc' => $f->clientCode, ':qid' => $f->glassQuestionId]);
        $decoded = json_decode((string)$glass->fetchColumn(), true);
        $this->assertSame(['pain' => 'mild'], $decoded);
        qdb_discard($tmpDb, $lockFp);
    }

    public function testResubmitCreatesSecondSubmissionVersion(): void
    {
        $login = $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::COACH_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $f = $this->fixture();

        $this->assertApiOk($this->submitFixtureQuestionnaire());
        $this->assertSame(1, $this->submissionVersionCount($f->clientCode, $f->questionnaireId));

        $res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
            ['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
        ]);
        $this->assertApiOk($res);
        $this->assertTrue($res['data']['submitted'] ?? false);
        $this->assertSame(2, $this->submissionVersionCount($f->clientCode, $f->questionnaireId));

        [$pdo, $tmpDb, $lockFp] = qdb_open(false);
        $stmt = $pdo->prepare(
            'SELECT COUNT(*) FROM client_answer_submission cas
             INNER JOIN questionnaire_submission qs ON qs.submissionID = cas.submissionID
             WHERE qs.clientCode = :cc AND qs.questionnaireID = :qn'
        );
        $stmt->execute([':cc' => $f->clientCode, ':qn' => $f->questionnaireId]);
        $this->assertGreaterThanOrEqual(2, (int)$stmt->fetchColumn());
        qdb_discard($tmpDb, $lockFp);
    }

    public function testSupervisorCanSubmitForClient(): void
    {
        $login = $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::SUPERVISOR_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $f = $this->fixture();
        $res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
            ['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
        ]);
        $this->assertApiOk($res);

        [$pdo, $tmpDb, $lockFp] = qdb_open(false);
        $stmt = $pdo->prepare(
            'SELECT submittedByRole FROM questionnaire_submission
             WHERE clientCode = :cc AND questionnaireID = :qn ORDER BY version DESC LIMIT 1'
        );
        $stmt->execute([':cc' => $f->clientCode, ':qn' => $f->questionnaireId]);
        $this->assertSame('supervisor', $stmt->fetchColumn());
        qdb_discard($tmpDb, $lockFp);
    }

    public function testSubmitWithFreeTextAndGlassSymptom(): void
    {
        $login = $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::COACH_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $f = $this->fixture();
        $res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
            ['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
            ['questionID' => $f->freeTextShortId, 'freeTextValue' => 'Feeling better'],
            ['questionID' => $f->glassSymptomKey, 'freeTextValue' => 'moderate'],
        ]);
        $this->assertApiOk($res);

        [$pdo, $tmpDb, $lockFp] = qdb_open(false);
        $ft = $pdo->prepare(
            'SELECT freeTextValue FROM client_answer WHERE clientCode = :cc AND questionID = :qid'
        );
        $ft->execute([':cc' => $f->clientCode, ':qid' => $f->freeTextQuestionId]);
        $this->assertSame('Feeling better', $ft->fetchColumn());

        $glass = $pdo->prepare(
            'SELECT freeTextValue FROM client_answer WHERE clientCode = :cc AND questionID = :qid'
        );
        $glass->execute([':cc' => $f->clientCode, ':qid' => $f->glassQuestionId]);
        $json = (string)$glass->fetchColumn();
        $decoded = json_decode($json, true);
        $this->assertIsArray($decoded);
        $this->assertSame('moderate', $decoded[$f->glassSymptomKey] ?? null);
        qdb_discard($tmpDb, $lockFp);
    }

    public function testCoachLoadsEncryptedAnswersBundle(): void
    {
        $this->submitFixtureQuestionnaire();
        $login = $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::COACH_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $f = $this->fixture();
        $res = $this->api()->withMobileToken($login['token'], 'GET', 'app_questionnaires', null, [
            'clientCode' => $f->clientCode,
            'answers'    => '1',
        ]);
        $this->assertApiOk($res);
        $this->assertTrue($res['data']['encrypted'] ?? false);
        $plain = qdb_decrypt_sensitive_envelope($res['data'], $login['token']);
        $payload = json_decode($plain, true, 512, JSON_THROW_ON_ERROR);
        $this->assertSame($f->clientCode, $payload['clientCode']);
        $this->assertNotEmpty($payload['questionnaires']);
    }

    public function testMultiCheckSubmitAccumulatesPoints(): void
    {
        $login = $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::COACH_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $f = $this->fixture();
        $res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
            ['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
            ['questionID' => $f->multiCheckShortId, 'answerOptionKey' => 'opt_a'],
            ['questionID' => $f->multiCheckShortId, 'answerOptionKey' => 'opt_b'],
        ]);
        $this->assertApiOk($res);
        $this->assertSame(3, $res['data']['sumPoints'] ?? 0);
    }

    public function testRequiredQuestionMissingFailsValidation(): void
    {
        $login = $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::COACH_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $f = $this->fixture();
        $res = $this->submitQuestionnaireAs($login['token'], $f->questionnaireId, $f->clientCode, [
            ['questionID' => $f->freeTextShortId, 'freeTextValue' => 'skipped consent'],
        ]);
        $this->assertApiError($res, 'VALIDATION_FAILED', 400);
        $codes = array_column($res['error']['details']['errors'] ?? [], 'code');
        $this->assertContains('MISSING_ANSWER', $codes);
    }

    public function testActiveListExcludesDraftQuestionnaire(): void
    {
        $login = $this->api()->loginMobileAndGetToken(
            DatabaseSeeder::COACH_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $f = $this->fixture();
        $res = $this->api()->withMobileToken($login['token'], 'GET', 'app_questionnaires');
        $this->assertApiOk($res);
        $ids = array_column($res['data'], 'id');
        $this->assertContains($f->questionnaireId, $ids);
        $this->assertNotContains($f->draftQuestionnaireId, $ids);
    }

    public function testTempTokenCannotSubmitQuestionnaire(): void
    {
        [$pdo, $tmpDb, $lockFp] = qdb_open(true);
        $f = $this->fixture();
        $pdo->prepare('UPDATE users SET mustChangePassword = 1 WHERE userID = :uid')
            ->execute([':uid' => $f->supervisorUserId]);
        qdb_save($tmpDb, $lockFp);

        $login = $this->api()->loginMobile(
            DatabaseSeeder::SUPERVISOR_USERNAME,
            DatabaseSeeder::PASSWORD
        );
        $this->assertApiOk($login);
        $tempToken = $login['data']['token'];
        $res = $this->api()->encryptedPost($tempToken, 'app_questionnaires', [
            'questionnaireID' => $f->questionnaireId,
            'clientCode'      => $f->clientCode,
            'answers'         => [
                ['questionID' => $f->questionShortId, 'answerOptionKey' => 'yes'],
            ],
        ]);
        $this->assertApiError($res, 'PASSWORD_CHANGE_REQUIRED', 403);
    }
}
