<?php

require_once __DIR__ . '/../lib/scoring.php';
require_once __DIR__ . '/../lib/questionnaire_structure.php';

$tokenRec = require_valid_token_web();

switch ($method) {

case 'GET':
    $qnID = $_GET['questionnaireID'] ?? '';
    if (!$qnID) {
        json_error('BAD_REQUEST', 'questionnaireID query param required', 400);
    }
    try {
        $opened = qdb_open_read_or_fail();
        [$pdo, $tmpDb, $lockFp] = $opened;
        $includeRetired = !empty($_GET['includeRetired']);
        $qWhere = 'questionnaireID = :qid';
        if (!$includeRetired) {
            $qWhere .= ' AND ' . qdb_active_questions_clause('question');
        }
        $stmt = $pdo->prepare(
            "SELECT questionID, questionnaireID, defaultText, type, orderIndex, isRequired, configJson,
                    retiredAt, retiredInRevision
             FROM question WHERE $qWhere ORDER BY orderIndex"
        );
    $stmt->execute([':qid' => $qnID]);
    $questions = $stmt->fetchAll(PDO::FETCH_ASSOC);
    $structureRevision = qdb_questionnaire_structure_revision($pdo, $qnID);
    foreach ($questions as &$q) {
        $q['isRequired'] = (int)$q['isRequired'];
        $q['orderIndex'] = (int)$q['orderIndex'];
        $q['retiredAt'] = (int)($q['retiredAt'] ?? 0);
        $q['retiredInRevision'] = (int)($q['retiredInRevision'] ?? 0);
        $q['retired'] = $q['retiredAt'] > 0;
        $cfg = qdb_parse_config_json($q['configJson']);
        $q['configJson'] = json_encode($cfg, JSON_UNESCAPED_UNICODE);
        $q['questionKey'] = qdb_question_key($cfg, $q['defaultText']);
        $q['localId'] = qdb_question_local_id($q['questionID'], $qnID);
        $ao = $pdo->prepare(
            "SELECT answerOptionID, questionID, defaultText, points, orderIndex, nextQuestionId, retiredAt
             FROM answer_option WHERE questionID = :qid AND " . qdb_active_options_clause('answer_option') . '
             ORDER BY orderIndex'
        );
        $ao->execute([':qid' => $q['questionID']]);
        $q['answerOptions'] = $ao->fetchAll(PDO::FETCH_ASSOC);
        foreach ($q['answerOptions'] as &$opt) {
            $opt['points']     = (int)$opt['points'];
            $opt['orderIndex'] = (int)$opt['orderIndex'];
            $opt['optionKey'] = qdb_option_key($opt);
            $opt['labelGerman'] = qdb_option_german_label($pdo, $opt['answerOptionID'], $opt['defaultText']);
        }
        unset($opt);
        if (($q['type'] ?? '') === 'glass_scale_question') {
            $q['glassSymptoms'] = qdb_glass_symptoms_with_score_rules($pdo, $q['questionID'], $cfg);
        } elseif (in_array($q['type'] ?? '', ['slider_question', 'value_spinner'], true)) {
            $q['scoreRules'] = qdb_get_score_rules_for_question($pdo, $q['questionID']);
        }
        $tr = $pdo->prepare("SELECT languageCode, text FROM question_translation WHERE questionID = :qid");
        $tr->execute([':qid' => $q['questionID']]);
        $q['translations'] = $tr->fetchAll(PDO::FETCH_ASSOC);
    }
        unset($q);
        qdb_discard($tmpDb, $lockFp);
        json_success([
            'questions'          => $questions,
            'structureRevision'  => $structureRevision,
        ]);
    } catch (Throwable $e) {
        qdb_handler_fail($e, 'Load questions', null, $tmpDb ?? null, $lockFp ?? null);
    }
    break;

case 'POST':
    require_role(['admin', 'supervisor'], $tokenRec);
    $body = read_json_body();
    if (empty($body['questionnaireID']) || !isset($body['defaultText']) || empty($body['questionKey'])) {
        json_error('BAD_REQUEST', 'questionnaireID, questionKey, and defaultText required', 400);
    }
    $qnID   = $body['questionnaireID'];
    $text   = trim($body['defaultText']);
    qdb_require_non_empty_german($text);
    $questionKey = qdb_validate_stable_key((string)$body['questionKey'], 'Question key');
    $type   = trim($body['type'] ?? '');
    $order  = (int)($body['orderIndex'] ?? 0);
    $req    = (int)($body['isRequired'] ?? 0);
    $cfg = qdb_parse_config_json($body['configJson'] ?? '{}');
    $glassSymptoms = qdb_parse_glass_symptoms_body($body, $cfg);
    if ($type === 'glass_scale_question') {
        $cfg = qdb_apply_glass_symptoms_config($cfg, $glassSymptoms);
    }
    $config = qdb_normalize_question_config(
        $cfg,
        $questionKey,
        $body['noteBefore'] ?? ($cfg['noteBefore'] ?? null),
        $body['noteAfter'] ?? ($cfg['noteAfter'] ?? null)
    );
    $configJson = json_encode($config, JSON_UNESCAPED_UNICODE);
    [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
    try {
        $chk = $pdo->prepare("SELECT 1 FROM questionnaire WHERE questionnaireID = :id");
        $chk->execute([':id' => $qnID]);
        if (!$chk->fetch()) {
            qdb_discard($tmpDb, $lockFp);
            json_error('NOT_FOUND', 'Questionnaire not found', 404);
        }
        if ($order === 0) {
            $max = $pdo->prepare("SELECT COALESCE(MAX(orderIndex),0)+1 FROM question WHERE questionnaireID = :id");
            $max->execute([':id' => $qnID]);
            $order = (int)$max->fetchColumn();
        }
        $localId = trim($body['localId'] ?? '');
        if ($localId === '') {
            $localId = qdb_allocate_question_local_id($pdo, $qnID);
        }
        $id = qdb_make_question_id($qnID, $localId);
        $dup = $pdo->prepare('SELECT 1 FROM question WHERE questionID = :id');
        $dup->execute([':id' => $id]);
        if ($dup->fetch()) {
            qdb_discard($tmpDb, $lockFp);
            json_error('CONFLICT', "Question id \"$localId\" already exists in this questionnaire", 409);
        }
        $pdo->prepare("INSERT INTO question (questionID, questionnaireID, defaultText, type, orderIndex, isRequired, configJson)
                        VALUES (:id, :qid, :t, :ty, :o, :r, :cj)")
            ->execute([':id' => $id, ':qid' => $qnID, ':t' => $text, ':ty' => $type,
                       ':o' => $order, ':r' => $req, ':cj' => $configJson]);
        qdb_upsert_source_translation($pdo, 'question', $id, $text);
        qdb_sync_question_note_strings($pdo, $questionKey, $config);
        if ($type === 'glass_scale_question') {
            qdb_sync_glass_symptom_strings($pdo, $glassSymptoms);
        }
        $scoreRules = qdb_parse_score_rules_body($body, $type, $config);
        if ($scoreRules !== [] || in_array($type, ['slider_question', 'value_spinner', 'glass_scale_question'], true)) {
            qdb_sync_question_score_rules($pdo, $id, $scoreRules);
        }
        $newRev = qdb_bump_structure_revision($pdo, $qnID, 'question_added');
        qdb_save($tmpDb, $lockFp);
        json_success([
            'structureRevision' => $newRev,
            'question' => [
            'questionID' => $id, 'questionnaireID' => $qnID, 'defaultText' => $text,
            'questionKey' => $questionKey, 'localId' => $localId,
            'type' => $type, 'orderIndex' => $order, 'isRequired' => $req,
            'configJson' => $configJson, 'answerOptions' => [], 'translations' => [],
            'glassSymptoms' => $type === 'glass_scale_question'
                ? qdb_glass_symptoms_with_score_rules($pdo, $id, $config) : [],
            'scoreRules' => in_array($type, ['slider_question', 'value_spinner'], true)
                ? qdb_get_score_rules_for_question($pdo, $id) : [],
            ],
        ]);
    } catch (Throwable $e) {
        qdb_handler_fail($e, 'Questions', null, $tmpDb ?? null, $lockFp ?? null);
    }
    break;

case 'PUT':
    require_role(['admin', 'supervisor'], $tokenRec);
    $body = read_json_body();
    if (empty($body['questionID'])) {
        json_error('BAD_REQUEST', 'questionID is required', 400);
    }
    $id = $body['questionID'];
    [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
    try {
        $existing = $pdo->prepare("SELECT * FROM question WHERE questionID = :id");
        $existing->execute([':id' => $id]);
        $row = $existing->fetch(PDO::FETCH_ASSOC);
        if (!$row) {
            qdb_discard($tmpDb, $lockFp);
            json_error('NOT_FOUND', 'Question not found', 404);
        }
        $text   = trim($body['defaultText'] ?? $row['defaultText']);
        qdb_require_non_empty_german($text);
        $type   = trim($body['type']        ?? $row['type']);
        $order  = (int)($body['orderIndex'] ?? $row['orderIndex']);
        $req    = (int)($body['isRequired'] ?? $row['isRequired']);
        $oldCfg = qdb_parse_config_json($row['configJson']);
        $questionKey = isset($body['questionKey'])
            ? qdb_validate_stable_key((string)$body['questionKey'], 'Question key')
            : qdb_question_key($oldCfg, $row['defaultText']);
        if ($questionKey === '') {
            json_error('MISSING_FIELDS', 'questionKey is required', 400);
        }
        $cfgIn = isset($body['configJson']) ? qdb_parse_config_json($body['configJson']) : $oldCfg;
        $glassSymptoms = qdb_parse_glass_symptoms_body($body, $cfgIn);
        if ($type === 'glass_scale_question') {
            $cfgIn = qdb_apply_glass_symptoms_config($cfgIn, $glassSymptoms);
        }
        $config = qdb_normalize_question_config(
            $cfgIn,
            $questionKey,
            $body['noteBefore'] ?? ($cfgIn['noteBefore'] ?? null),
            $body['noteAfter'] ?? ($cfgIn['noteAfter'] ?? null)
        );
        $configJson = json_encode($config, JSON_UNESCAPED_UNICODE);
        $oldKey = qdb_question_key($oldCfg, $row['defaultText']);
        $oldSymptoms = json_encode($oldCfg['symptoms'] ?? [], JSON_UNESCAPED_UNICODE);
        $newSymptoms = json_encode($config['symptoms'] ?? [], JSON_UNESCAPED_UNICODE);
        // Notes live in configJson and are exported as noteBeforeKey/noteAfterKey + translations.
        // App sync skips detail re-download when structureRevision is unchanged, so note-only
        // (and other app-visible config) edits must bump the revision.
        $oldNotes = trim((string)($oldCfg['noteBefore'] ?? '')) . "\0" . trim((string)($oldCfg['noteAfter'] ?? ''));
        $newNotes = trim((string)($config['noteBefore'] ?? '')) . "\0" . trim((string)($config['noteAfter'] ?? ''));
        $structuralChange = ($type !== (string)$row['type'])
            || ($req !== (int)$row['isRequired'])
            || ($questionKey !== $oldKey)
            || ($newSymptoms !== $oldSymptoms)
            || ($oldNotes !== $newNotes)
            || (json_encode($config, JSON_UNESCAPED_UNICODE) !== json_encode($oldCfg, JSON_UNESCAPED_UNICODE));
        if ((int)($row['retiredAt'] ?? 0) > 0) {
            qdb_discard($tmpDb, $lockFp);
            json_error('INVALID_FIELD', 'Cannot edit a retired question', 400);
        }
        $pdo->prepare("UPDATE question SET defaultText = :t, type = :ty, orderIndex = :o, isRequired = :r, configJson = :cj WHERE questionID = :id")
            ->execute([':t' => $text, ':ty' => $type, ':o' => $order, ':r' => $req, ':cj' => $configJson, ':id' => $id]);
        qdb_upsert_source_translation($pdo, 'question', $id, $text);
        qdb_sync_question_note_strings($pdo, $questionKey, $config);
        if ($type === 'glass_scale_question') {
            qdb_sync_glass_symptom_strings($pdo, $glassSymptoms);
        }
        $scoreRules = qdb_parse_score_rules_body($body, $type, $config);
        if ($scoreRules !== [] || in_array($type, ['slider_question', 'value_spinner', 'glass_scale_question'], true)) {
            qdb_sync_question_score_rules($pdo, $id, $scoreRules);
        }
        $newRev = null;
        if ($structuralChange) {
            $newRev = qdb_bump_structure_revision($pdo, (string)$row['questionnaireID'], 'question_updated');
        }
        qdb_save($tmpDb, $lockFp);
        json_success([
            'structureRevision' => $newRev ?? qdb_questionnaire_structure_revision($pdo, (string)$row['questionnaireID']),
            'question' => [
            'questionID' => $id, 'questionnaireID' => $row['questionnaireID'],
            'defaultText' => $text, 'questionKey' => $questionKey,
            'type' => $type, 'orderIndex' => $order, 'isRequired' => $req,
            'configJson' => $configJson,
            'glassSymptoms' => $type === 'glass_scale_question'
                ? qdb_glass_symptoms_with_score_rules($pdo, $id, $config) : [],
            'scoreRules' => in_array($type, ['slider_question', 'value_spinner'], true)
                ? qdb_get_score_rules_for_question($pdo, $id) : [],
            ],
        ]);
    } catch (Throwable $e) {
        qdb_handler_fail($e, 'Questions', null, $tmpDb ?? null, $lockFp ?? null);
    }
    break;

case 'DELETE':
    require_role(['admin', 'supervisor'], $tokenRec);
    $body = read_json_body();
    if (empty($body['questionID'])) {
        json_error('BAD_REQUEST', 'questionID is required', 400);
    }
    $id = $body['questionID'];
    [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
    try {
        $existing = $pdo->prepare('SELECT questionnaireID, retiredAt FROM question WHERE questionID = :id');
        $existing->execute([':id' => $id]);
        $row = $existing->fetch(PDO::FETCH_ASSOC);
        if (!$row) {
            qdb_discard($tmpDb, $lockFp);
            json_error('NOT_FOUND', 'Question not found', 404);
        }
        $qnID = (string)$row['questionnaireID'];
        if ((int)($row['retiredAt'] ?? 0) > 0) {
            qdb_discard($tmpDb, $lockFp);
            json_success(['deleted' => false, 'retired' => true, 'structureRevision' => qdb_questionnaire_structure_revision($pdo, $qnID)]);
            break;
        }

        $pdo->beginTransaction();
        $newRev = qdb_bump_structure_revision($pdo, $qnID, 'question_removed');

        if (qdb_question_has_client_data($pdo, $id)) {
            qdb_retire_question($pdo, $id, $newRev);
            $pdo->commit();
            qdb_save($tmpDb, $lockFp);
            json_success([
                'deleted'           => false,
                'retired'           => true,
                'structureRevision' => $newRev,
            ]);
            break;
        }

        qdb_set_foreign_keys($pdo, false);
        $aoIds = $pdo->prepare('SELECT answerOptionID FROM answer_option WHERE questionID = :id');
        $aoIds->execute([':id' => $id]);
        foreach ($aoIds->fetchAll(PDO::FETCH_COLUMN) as $aoid) {
            $pdo->prepare('DELETE FROM answer_option_translation WHERE answerOptionID = :id')->execute([':id' => $aoid]);
        }
        $pdo->prepare('DELETE FROM answer_option WHERE questionID = :id')->execute([':id' => $id]);
        $pdo->prepare('DELETE FROM question_score_rule WHERE questionID = :id')->execute([':id' => $id]);
        $pdo->prepare('DELETE FROM question_translation WHERE questionID = :id')->execute([':id' => $id]);
        $pdo->prepare('DELETE FROM question WHERE questionID = :id')->execute([':id' => $id]);
        qdb_set_foreign_keys($pdo, true);
        $pdo->commit();
        qdb_save($tmpDb, $lockFp);
        json_success([
            'deleted'           => true,
            'retired'           => false,
            'structureRevision' => $newRev,
        ]);
    } catch (Throwable $e) {
        qdb_handler_fail($e, 'Delete question', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null);
    }
    break;

case 'PATCH':
    require_role(['admin', 'supervisor'], $tokenRec);
    $body = read_json_body();
    if (empty($body['questionnaireID']) || !is_array($body['order'] ?? null)) {
        json_error('BAD_REQUEST', 'questionnaireID and order[] required', 400);
    }
    $qnID = (string)$body['questionnaireID'];
    $orderIds = array_values(array_filter(
        array_map('strval', $body['order']),
        static fn($id) => $id !== ''
    ));
    if ($orderIds === []) {
        json_error('BAD_REQUEST', 'order[] must list at least one questionID', 400);
    }
    try {
        $opened = qdb_open_write_or_fail();
        [$pdo, $tmpDb, $lockFp] = $opened;
        $chk = $pdo->prepare('SELECT 1 FROM questionnaire WHERE questionnaireID = :id');
        $chk->execute([':id' => $qnID]);
        if (!$chk->fetch()) {
            qdb_discard($tmpDb, $lockFp);
            json_error('NOT_FOUND', 'Questionnaire not found', 404);
        }
        $ph = implode(',', array_fill(0, count($orderIds), '?'));
        $stmt = $pdo->prepare(
            "SELECT questionID FROM question WHERE questionnaireID = ? AND questionID IN ($ph)"
        );
        $stmt->execute(array_merge([$qnID], $orderIds));
        $found = $stmt->fetchAll(PDO::FETCH_COLUMN);
        if (count($found) !== count($orderIds)) {
            $missing = array_values(array_diff($orderIds, $found));
            qdb_discard($tmpDb, $lockFp);
            json_error(
                'INVALID_FIELD',
                'order[] contains questionIDs not in this questionnaire',
                400,
                ['missingQuestionIDs' => $missing]
            );
        }
        $upd = $pdo->prepare(
            'UPDATE question SET orderIndex = :o WHERE questionID = :id AND questionnaireID = :qid'
        );
        foreach ($orderIds as $idx => $qid) {
            $upd->execute([':o' => $idx, ':id' => $qid, ':qid' => $qnID]);
        }
        qdb_save($tmpDb, $lockFp);
        json_success(['reordered' => true]);
    } catch (Throwable $e) {
        qdb_handler_fail($e, 'Reorder questions', null, $tmpDb ?? null, $lockFp ?? null);
    }
    break;

default:
    json_error('METHOD_NOT_ALLOWED', 'Method not allowed', 405);
}
