<?php

/**
 * Unit tests for AI repair JSON normalizer (no OpenAI calls).
 * Usage: php tools/test_ai_repair_normalize.php
 */

require dirname(__DIR__) . '/app/ai/form_generator_question_validation.php';
require dirname(__DIR__) . '/prompts/sba_ai_repair_prompt.php';

$passed = 0;
$failed = 0;

function repairTest(bool $ok, string $label): void
{
    global $passed, $failed;
    if ($ok) {
        echo 'PASS: ' . $label . PHP_EOL;
        ++$passed;
    } else {
        echo 'FAIL: ' . $label . PHP_EOL;
        ++$failed;
    }
}

$original = [
    'title' => 'CAP emergency',
    'stem' => 'SpO2 88% on air.',
    'lead_in' => 'What is the most appropriate immediate management?',
    'option_a' => 'IV fluids and antibiotics',
    'option_b' => 'Oxygen therapy',
    'option_c' => 'Observe',
    'option_d' => 'Oral antibiotics',
    'option_e' => 'Chest X-ray',
    'correct_answer_letter' => 'A',
    'correct_answer' => 'IV fluids and antibiotics',
    'justification' => 'Resuscitation required.',
    'skill' => 'Emergency Management',
    'specialty' => 'Acute Medicine',
    'condition_name' => 'Community-acquired pneumonia',
    'presentation' => 'Severe pneumonia',
    'year_level' => 'MLA Final Year',
];

$validRaw = [
    'repaired_question' => [
        'title' => 'CAP emergency resuscitation',
        'stem' => 'SpO2 88% on air.',
        'lead_in' => 'What is the most appropriate immediate management?',
        'option_a' => 'Give oxygen, start IV fluids and broad-spectrum IV antibiotics',
        'option_b' => 'Oxygen therapy only',
        'option_c' => 'Observe on the ward',
        'option_d' => 'Oral antibiotics',
        'option_e' => 'Chest X-ray',
        'correct_answer_letter' => 'A',
        'correct_answer' => 'Give oxygen, start IV fluids and broad-spectrum IV antibiotics',
        'justification' => 'Hypoxic sepsis requires oxygen plus resuscitation.',
    ],
    'change_log' => [
        [
            'field' => 'option_a',
            'before' => 'IV fluids and antibiotics',
            'after' => 'Give oxygen, start IV fluids and broad-spectrum IV antibiotics',
            'reason' => 'Include oxygen for hypoxia',
            'change_type' => 'clinical',
            'risk' => 'high',
        ],
    ],
    'repair_risk_level' => 'high',
    'any_clinical_content_changed' => true,
    'repair_summary' => 'Added oxygen to keyed emergency bundle.',
];

$norm = normalizeSbaAiRepairResponse($validRaw, $original);
repairTest($norm['ok'] === true, 'valid repair JSON normalizes');
repairTest(
    $norm['ok'] && ($norm['data']['repair_risk_level'] ?? '') === 'high',
    'high risk preserved when clinical key changes'
);
repairTest(
    $norm['ok'] && ($norm['data']['any_clinical_content_changed'] ?? false) === true,
    'clinical content flagged when oxygen added to bundle'
);

$badLetter = $validRaw;
$badLetter['repaired_question']['correct_answer_letter'] = 'Z';
$badNorm = normalizeSbaAiRepairResponse($badLetter, $original);
repairTest($badNorm['ok'] === false, 'invalid letter fails closed');

$missing = ['repair_summary' => 'x'];
$missingNorm = normalizeSbaAiRepairResponse($missing, $original);
repairTest($missingNorm['ok'] === false, 'missing repaired_question fails closed');

$rephraseOriginal = $original;
$rephraseOriginal['option_a'] = 'Start intravenous fluids and broad-spectrum IV antibiotics, and provide oxygen therapy.';
$rephraseOriginal['correct_answer'] = $rephraseOriginal['option_a'];

$rephraseAfter = 'Start intravenous fluids, administer broad-spectrum IV antibiotics, and provide oxygen therapy.';

$rephraseRaw = [
    'repaired_question' => [
        'title' => $rephraseOriginal['title'],
        'stem' => $rephraseOriginal['stem'],
        'lead_in' => $rephraseOriginal['lead_in'],
        'option_a' => $rephraseAfter,
        'option_b' => $rephraseOriginal['option_b'],
        'option_c' => $rephraseOriginal['option_c'],
        'option_d' => $rephraseOriginal['option_d'],
        'option_e' => $rephraseOriginal['option_e'],
        'correct_answer_letter' => 'A',
        'correct_answer' => $rephraseAfter,
        'justification' => $rephraseOriginal['justification'],
    ],
    'change_log' => [
        [
            'field' => 'option_a',
            'before' => $rephraseOriginal['option_a'],
            'after' => $rephraseAfter,
            'reason' => 'Clearer parallel wording',
            'change_type' => 'clinical',
            'risk' => 'high',
        ],
    ],
    'repair_risk_level' => 'high',
    'any_clinical_content_changed' => true,
    'repair_summary' => 'Rephrased option A wording.',
];

$rephraseNorm = normalizeSbaAiRepairResponse($rephraseRaw, $rephraseOriginal);
repairTest($rephraseNorm['ok'] === true, 'rephrase-only repair normalizes');
repairTest(
    $rephraseNorm['ok'] && ($rephraseNorm['data']['any_clinical_content_changed'] ?? true) === false,
    'rephrase-only CAP bundle: any_clinical_content_changed false'
);
repairTest(
    $rephraseNorm['ok'] && ($rephraseNorm['data']['repair_risk_level'] ?? '') === 'low',
    'rephrase-only CAP bundle: repair_risk_level low'
);
$firstLogType = $rephraseNorm['data']['change_log'][0]['change_type'] ?? '';
repairTest(
    $rephraseNorm['ok'] && in_array($firstLogType, ['style', 'structure'], true),
    'rephrase-only: change_log downgraded to style/structure'
);

$letterChangeOriginal = $rephraseOriginal;
$letterChangeRaw = [
    'repaired_question' => [
        'title' => $letterChangeOriginal['title'],
        'stem' => $letterChangeOriginal['stem'],
        'lead_in' => $letterChangeOriginal['lead_in'],
        'option_a' => $letterChangeOriginal['option_a'],
        'option_b' => $letterChangeOriginal['option_b'],
        'option_c' => $letterChangeOriginal['option_c'],
        'option_d' => $letterChangeOriginal['option_d'],
        'option_e' => $letterChangeOriginal['option_e'],
        'correct_answer_letter' => 'B',
        'correct_answer' => $letterChangeOriginal['option_b'],
        'justification' => $letterChangeOriginal['justification'],
    ],
    'change_log' => [
        [
            'field' => 'correct_answer_letter',
            'before' => 'A',
            'after' => 'B',
            'reason' => 'Reviewer key correction',
            'change_type' => 'clinical',
            'risk' => 'high',
        ],
    ],
    'repair_risk_level' => 'low',
    'any_clinical_content_changed' => false,
    'repair_summary' => 'Correct answer letter updated.',
];

$letterNorm = normalizeSbaAiRepairResponse($letterChangeRaw, $letterChangeOriginal);
repairTest($letterNorm['ok'] === true, 'letter change repair normalizes');
repairTest(
    $letterNorm['ok'] && ($letterNorm['data']['any_clinical_content_changed'] ?? false) === true,
    'letter change: any_clinical_content_changed true'
);
repairTest(
    $letterNorm['ok'] && ($letterNorm['data']['repair_risk_level'] ?? '') === 'high',
    'letter change: repair_risk_level high'
);

$distractorOriginal = $original;
$distractorOriginal['option_c'] = 'Treat as community-acquired pneumonia with oral antibiotics';
$distractorOriginal['correct_answer_letter'] = 'A';
$distractorOriginal['correct_answer'] = $distractorOriginal['option_a'];

$distractorRaw = [
    'repaired_question' => [
        'title' => $distractorOriginal['title'],
        'stem' => $distractorOriginal['stem'],
        'lead_in' => $distractorOriginal['lead_in'],
        'option_a' => $distractorOriginal['option_a'],
        'option_b' => $distractorOriginal['option_b'],
        'option_c' => 'Treat as anaphylaxis with intramuscular adrenaline',
        'option_d' => $distractorOriginal['option_d'],
        'option_e' => $distractorOriginal['option_e'],
        'correct_answer_letter' => 'A',
        'correct_answer' => $distractorOriginal['correct_answer'],
        'justification' => $distractorOriginal['justification'],
    ],
    'change_log' => [
        [
            'field' => 'option_c',
            'before' => $distractorOriginal['option_c'],
            'after' => 'Treat as anaphylaxis with intramuscular adrenaline',
            'reason' => 'Clearer distractor wording',
            'change_type' => 'style',
            'risk' => 'low',
        ],
    ],
    'repair_risk_level' => 'low',
    'any_clinical_content_changed' => false,
    'repair_summary' => 'Updated distractor C wording.',
];

$distractorNorm = normalizeSbaAiRepairResponse($distractorRaw, $distractorOriginal);
repairTest($distractorNorm['ok'] === true, 'distractor condition swap normalizes');
repairTest(
    $distractorNorm['ok'] && ($distractorNorm['data']['any_clinical_content_changed'] ?? false) === true,
    'pneumonia→anaphylaxis distractor: any_clinical_content_changed true'
);
$distractorLogType = $distractorNorm['data']['change_log'][0]['change_type'] ?? '';
repairTest(
    $distractorNorm['ok'] && $distractorLogType === 'educational_content',
    'mislabelled style upgraded to educational_content for distractor condition swap'
);
repairTest(
    $distractorNorm['ok'] && ($distractorNorm['data']['repair_risk_level'] ?? '') === 'medium',
    'distractor-only educational swap: repair_risk_level medium'
);
repairTest(
    $distractorNorm['ok'] && ($distractorNorm['data']['educational_content_changed'] ?? false) === true,
    'distractor-only educational swap: educational_content_changed true'
);

$copdOriginal = [
    'title' => 'COPD exacerbation',
    'stem' => 'Worsening breathlessness and increased sputum.',
    'lead_in' => 'What is the most appropriate change to long-term therapy?',
    'option_a' => 'Increase salbutamol inhaler dose',
    'option_b' => 'Start tiotropium',
    'option_c' => 'Add oral prednisolone only',
    'option_d' => 'Stop all bronchodilators',
    'option_e' => 'Refer for pulmonary rehabilitation only',
    'correct_answer_letter' => 'B',
    'correct_answer' => 'Start tiotropium',
    'justification' => 'LAMA indicated when symptoms persist despite SABA.',
    'skill' => 'Prescribing',
    'specialty' => 'Respiratory',
    'condition_name' => 'COPD',
    'presentation' => 'Exacerbation',
    'year_level' => 'MLA Final Year',
];

$copdRaw = [
    'repaired_question' => array_merge($copdOriginal, [
        'option_a' => 'Increase ipratropium inhaler dose',
    ]),
    'change_log' => [
        [
            'field' => 'option_a',
            'before' => 'Increase salbutamol inhaler dose',
            'after' => 'Increase ipratropium inhaler dose',
            'reason' => 'Parallel wording',
            'change_type' => 'style',
            'risk' => 'low',
        ],
    ],
    'repair_risk_level' => 'low',
    'any_clinical_content_changed' => false,
    'repair_summary' => 'Option A wording aligned.',
];

$copdNorm = normalizeSbaAiRepairResponse($copdRaw, $copdOriginal);
repairTest(
    $copdNorm['ok'] && ($copdNorm['data']['change_log'][0]['change_type'] ?? '') === 'educational_content',
    'COPD drug swap mislabelled style upgraded to educational_content'
);

$copdBatchOriginal = [
    'title' => 'COPD prescribing',
    'stem' => 'Increasing breathlessness; salbutamol no longer sufficient.',
    'lead_in' => 'What is the most appropriate next step in management?',
    'option_a' => 'Start tiotropium',
    'option_b' => 'Pulmonary fibrosis follow-up',
    'option_c' => 'Treat as community-acquired pneumonia',
    'option_d' => 'Increase budesonide inhaler dose',
    'option_e' => 'Start theophylline',
    'correct_answer_letter' => 'A',
    'correct_answer' => 'Start tiotropium',
    'justification' => 'LAMA when symptoms persist despite SABA.',
    'skill' => 'Prescribing',
    'specialty' => 'Respiratory',
    'condition_name' => 'COPD',
    'presentation' => 'Stable COPD',
    'year_level' => 'MLA Final Year',
];

$copdBatchCases = [
    [
        'field' => 'option_b',
        'before' => 'Pulmonary fibrosis follow-up',
        'after' => 'Interstitial lung disease follow-up',
        'label' => 'fibrosis→ILD',
    ],
    [
        'field' => 'option_c',
        'before' => 'Treat as community-acquired pneumonia',
        'after' => 'Treat as anaphylaxis with intramuscular adrenaline',
        'label' => 'pneumonia→anaphylaxis',
    ],
    [
        'field' => 'option_d',
        'before' => 'Increase budesonide inhaler dose',
        'after' => 'Start oral corticosteroids',
        'label' => 'budesonide→oral steroids',
    ],
    [
        'field' => 'option_e',
        'before' => 'Start theophylline',
        'after' => 'Start long-acting muscarinic antagonist',
        'label' => 'theophylline→LAMA',
    ],
];

foreach ($copdBatchCases as $case) {
    $repaired = $copdBatchOriginal;
    $repaired[$case['field']] = $case['after'];
    $raw = [
        'repaired_question' => [
            'title' => $repaired['title'],
            'stem' => $repaired['stem'],
            'lead_in' => $repaired['lead_in'],
            'option_a' => $repaired['option_a'],
            'option_b' => $repaired['option_b'],
            'option_c' => $repaired['option_c'],
            'option_d' => $repaired['option_d'],
            'option_e' => $repaired['option_e'],
            'correct_answer_letter' => 'A',
            'correct_answer' => $repaired['correct_answer'],
            'justification' => $repaired['justification'],
        ],
        'change_log' => [
            [
                'field' => $case['field'],
                'before' => $case['before'],
                'after' => $case['after'],
                'reason' => 'Clearer wording',
                'change_type' => 'style',
                'risk' => 'low',
            ],
        ],
        'repair_risk_level' => 'low',
        'any_clinical_content_changed' => false,
        'repair_summary' => 'COPD batch distractor update.',
    ];
    $normCase = normalizeSbaAiRepairResponse($raw, $copdBatchOriginal);
    $logType = $normCase['data']['change_log'][0]['change_type'] ?? '';
    repairTest(
        $normCase['ok'] && in_array($logType, ['educational_content', 'clinical'], true),
        'COPD batch ' . $case['label'] . ': not labelled style'
    );
    repairTest(
        $normCase['ok'] && ($normCase['data']['repair_risk_level'] ?? '') === 'medium',
        'COPD batch ' . $case['label'] . ': medium risk'
    );
}

$keyedChangeOriginal = $copdBatchOriginal;
$keyedChangeRaw = [
    'repaired_question' => [
        'title' => $keyedChangeOriginal['title'],
        'stem' => $keyedChangeOriginal['stem'],
        'lead_in' => $keyedChangeOriginal['lead_in'],
        'option_a' => 'Start glycopyrronium',
        'option_b' => $keyedChangeOriginal['option_b'],
        'option_c' => $keyedChangeOriginal['option_c'],
        'option_d' => $keyedChangeOriginal['option_d'],
        'option_e' => $keyedChangeOriginal['option_e'],
        'correct_answer_letter' => 'A',
        'correct_answer' => 'Start glycopyrronium',
        'justification' => $keyedChangeOriginal['justification'],
    ],
    'change_log' => [
        [
            'field' => 'option_a',
            'before' => 'Start tiotropium',
            'after' => 'Start glycopyrronium',
            'reason' => 'Different LAMA',
            'change_type' => 'style',
            'risk' => 'low',
        ],
    ],
    'repair_risk_level' => 'low',
    'any_clinical_content_changed' => false,
    'repair_summary' => 'Keyed LAMA changed.',
];
$keyedNorm = normalizeSbaAiRepairResponse($keyedChangeRaw, $keyedChangeOriginal);
repairTest(
    $keyedNorm['ok'] && ($keyedNorm['data']['repair_risk_level'] ?? '') === 'high',
    'keyed option drug change: repair_risk_level high'
);

$oxygenOriginal = [
    'title' => 'COPD oxygen',
    'stem' => 'Type 2 respiratory failure risk.',
    'lead_in' => 'What is the most appropriate oxygen prescription?',
    'option_a' => 'Oxygen 2 L/min via nasal cannula with target SpO2 88–92%',
    'option_b' => 'Oxygen 15 L/min via non-rebreather mask',
    'option_c' => 'Room air only',
    'option_d' => 'High-flow oxygen without target range',
    'option_e' => 'Discharge without oxygen assessment',
    'correct_answer_letter' => 'A',
    'correct_answer' => 'Oxygen 2 L/min via nasal cannula with target SpO2 88–92%',
    'justification' => 'Controlled oxygen in COPD.',
    'skill' => 'Prescribing',
    'specialty' => 'Respiratory',
    'condition_name' => 'COPD',
    'presentation' => 'Hypoxia',
    'year_level' => 'MLA Final Year',
];

$oxygenRaw = [
    'repaired_question' => array_merge($oxygenOriginal, [
        'option_b' => 'Oxygen 4 L/min via Venturi mask at 28%',
    ]),
    'change_log' => [
        [
            'field' => 'option_b',
            'before' => 'Oxygen 15 L/min via non-rebreather mask',
            'after' => 'Oxygen 4 L/min via Venturi mask at 28%',
            'reason' => 'Clearer wording',
            'change_type' => 'style',
            'risk' => 'low',
        ],
    ],
    'repair_risk_level' => 'low',
    'any_clinical_content_changed' => false,
    'repair_summary' => 'Distractor B wording.',
];
$oxygenNorm = normalizeSbaAiRepairResponse($oxygenRaw, $oxygenOriginal);
repairTest(
    $oxygenNorm['ok'] && ($oxygenNorm['data']['change_log'][0]['change_type'] ?? '') === 'educational_content',
    'oxygen flow/device change not labelled style'
);
repairTest(
    $oxygenNorm['ok'] && ($oxygenNorm['data']['repair_risk_level'] ?? '') === 'medium',
    'oxygen flow/device change: medium risk'
);

$escalationOriginal = $oxygenOriginal;
$escalationRaw = [
    'repaired_question' => array_merge($escalationOriginal, [
        'option_c' => 'Arrange arterial blood gas and consider non-invasive ventilation',
    ]),
    'change_log' => [
        [
            'field' => 'option_c',
            'before' => 'Room air only',
            'after' => 'Arrange arterial blood gas and consider non-invasive ventilation',
            'reason' => 'Wording tidy',
            'change_type' => 'style',
            'risk' => 'low',
        ],
    ],
    'repair_risk_level' => 'low',
    'any_clinical_content_changed' => false,
    'repair_summary' => 'Option C updated.',
];
$escalationNorm = normalizeSbaAiRepairResponse($escalationRaw, $escalationOriginal);
repairTest(
    $escalationNorm['ok'] && ($escalationNorm['data']['change_log'][0]['change_type'] ?? '') === 'educational_content',
    'ABG/NIV escalation change not labelled style'
);

$dvtDxOriginal = [
    'title' => 'Leg swelling diagnosis',
    'stem' => 'Unilateral calf swelling after a long flight.',
    'lead_in' => 'What is the most likely diagnosis?',
    'option_a' => 'Deep vein thrombosis',
    'option_b' => 'Cellulitis',
    'option_c' => 'Baker cyst',
    'option_d' => 'Musculoskeletal calf strain',
    'option_e' => 'Superficial thrombophlebitis',
    'correct_answer_letter' => 'A',
    'correct_answer' => 'Deep vein thrombosis',
    'justification' => 'Wells criteria and D-dimer pathway.',
    'skill' => 'Diagnosis',
    'specialty' => 'General Medicine',
    'condition_name' => 'Venous thromboembolic disease',
    'presentation' => 'DVT',
    'year_level' => 'MLA Final Year',
];

$dvtDxRaw = [
    'repaired_question' => array_merge($dvtDxOriginal, [
        'option_b' => 'Ruptured popliteal aneurysm',
    ]),
    'change_log' => [
        [
            'field' => 'option_b',
            'before' => 'Cellulitis',
            'after' => 'Ruptured popliteal aneurysm',
            'reason' => 'Clearer wording',
            'change_type' => 'style',
            'risk' => 'low',
        ],
    ],
    'repair_risk_level' => 'low',
    'any_clinical_content_changed' => false,
    'repair_summary' => 'Distractor B updated.',
];
$dvtDxNorm = normalizeSbaAiRepairResponse($dvtDxRaw, $dvtDxOriginal);
repairTest(
    $dvtDxNorm['ok'] && ($dvtDxNorm['data']['change_log'][0]['change_type'] ?? '') === 'educational_content',
    'DVT diagnosis distractor swap not labelled style'
);

$dvtUsOriginal = $dvtDxOriginal;
$dvtUsOriginal['lead_in'] = 'What is the most likely finding on compression ultrasound?';
$dvtUsOriginal['skill'] = 'Investigation';
$dvtUsRaw = [
    'repaired_question' => array_merge($dvtUsOriginal, [
        'option_c' => 'Chronic non-compressible thrombus',
        'correct_answer' => $dvtUsOriginal['correct_answer'],
    ]),
    'change_log' => [
        [
            'field' => 'option_c',
            'before' => 'Baker cyst',
            'after' => 'Chronic non-compressible thrombus',
            'reason' => 'Parallel structure',
            'change_type' => 'style',
            'risk' => 'low',
        ],
    ],
    'repair_risk_level' => 'low',
    'any_clinical_content_changed' => false,
    'repair_summary' => 'Ultrasound option C updated.',
];
$dvtUsNorm = normalizeSbaAiRepairResponse($dvtUsRaw, $dvtUsOriginal);
repairTest(
    $dvtUsNorm['ok'] && ($dvtUsNorm['data']['change_log'][0]['change_type'] ?? '') === 'educational_content',
    'DVT ultrasound interpretation distractor not labelled style'
);

$vteBatchBase = [
    'title' => 'Leg swelling VTE',
    'stem' => 'Unilateral leg swelling.',
    'lead_in' => 'What is the most likely diagnosis?',
    'option_a' => 'Proximal deep vein thrombosis',
    'option_b' => 'Lymphoedema',
    'option_c' => "Baker's cyst rupture",
    'option_d' => 'Chronic venous insufficiency',
    'option_e' => 'Cellulitis',
    'correct_answer_letter' => 'A',
    'correct_answer' => 'Proximal deep vein thrombosis',
    'justification' => 'Wells and imaging pathway.',
    'skill' => 'Diagnosis',
    'specialty' => 'General Medicine',
    'condition_name' => 'Venous thromboembolic disease',
    'presentation' => 'DVT',
    'year_level' => 'MLA Final Year',
];

$vteBatchSwaps = [
    ['option_b', 'Lymphoedema', 'Superficial thrombophlebitis', 'lymphoedema→superficial thrombophlebitis'],
    ['option_c', "Baker's cyst rupture", 'Osteomyelitis', 'Baker cyst→osteomyelitis'],
    ['option_d', 'Chronic venous insufficiency', 'Peripheral arterial occlusion', 'CVI→PAO'],
    ['option_a', 'Proximal deep vein thrombosis', 'Acute limb ischaemia', 'proximal DVT→limb ischaemia'],
];

foreach ($vteBatchSwaps as $swap) {
    [$field, $before, $after, $label] = $swap;
    $repaired = $vteBatchBase;
    $repaired[$field] = $after;
    if ($field === 'option_a') {
        $repaired['correct_answer'] = $after;
    }
    $raw = [
        'repaired_question' => [
            'title' => $repaired['title'],
            'stem' => $repaired['stem'],
            'lead_in' => $repaired['lead_in'],
            'option_a' => $repaired['option_a'],
            'option_b' => $repaired['option_b'],
            'option_c' => $repaired['option_c'],
            'option_d' => $repaired['option_d'],
            'option_e' => $repaired['option_e'],
            'correct_answer_letter' => $repaired['correct_answer_letter'],
            'correct_answer' => $repaired['correct_answer'],
            'justification' => $repaired['justification'],
        ],
        'change_log' => [
            [
                'field' => $field,
                'before' => $before,
                'after' => $after,
                'reason' => 'Wording',
                'change_type' => 'style',
                'risk' => 'low',
            ],
        ],
        'repair_risk_level' => 'low',
        'any_clinical_content_changed' => false,
        'repair_summary' => 'VTE batch swap.',
    ];
    $norm = normalizeSbaAiRepairResponse($raw, $vteBatchBase);
    $type = $norm['data']['change_log'][0]['change_type'] ?? '';
    repairTest(
        $norm['ok'] && in_array($type, ['educational_content', 'clinical'], true),
        'VTE batch ' . $label . ': not style'
    );
    $expectedRisk = ($field === 'option_a' && $vteBatchBase['correct_answer_letter'] === 'A') ? 'high' : 'medium';
    repairTest(
        $norm['ok'] && ($norm['data']['repair_risk_level'] ?? '') === $expectedRisk,
        'VTE batch ' . $label . ': ' . $expectedRisk . ' risk'
    );
}

$usBase = $vteBatchBase;
$usBase['lead_in'] = 'What is the most likely ultrasound finding?';
$usBase['skill'] = 'Investigation';
$usSwaps = [
    ['option_b', 'Normal venous ultrasound', 'Post-thrombotic syndrome', 'normal US→PTS'],
    ['option_c', 'Chronic venous insufficiency', 'No deep vein thrombosis', 'CVI→no DVT'],
];
foreach ($usSwaps as $swap) {
    [$field, $before, $after, $label] = $swap;
    $repaired = $usBase;
    $repaired[$field] = $after;
    $raw = [
        'repaired_question' => [
            'title' => $repaired['title'],
            'stem' => $repaired['stem'],
            'lead_in' => $repaired['lead_in'],
            'option_a' => $repaired['option_a'],
            'option_b' => $repaired['option_b'],
            'option_c' => $repaired['option_c'],
            'option_d' => $repaired['option_d'],
            'option_e' => $repaired['option_e'],
            'correct_answer_letter' => 'A',
            'correct_answer' => $repaired['correct_answer'],
            'justification' => $repaired['justification'],
        ],
        'change_log' => [
            [
                'field' => $field,
                'before' => $before,
                'after' => $after,
                'reason' => 'Wording',
                'change_type' => 'style',
                'risk' => 'low',
            ],
        ],
        'repair_risk_level' => 'low',
        'any_clinical_content_changed' => false,
        'repair_summary' => 'US batch swap.',
    ];
    $norm = normalizeSbaAiRepairResponse($raw, $usBase);
    repairTest(
        $norm['ok'] && in_array($norm['data']['change_log'][0]['change_type'] ?? '', ['educational_content', 'clinical'], true),
        'VTE US ' . $label . ': not style'
    );
}

$monBase = $vteBatchBase;
$monBase['lead_in'] = 'What is the most appropriate monitoring test?';
$monBase['skill'] = 'Monitoring';
$labSwaps = [
    ['option_b', 'Full blood count', 'Prothrombin time', 'FBC→PT'],
    ['option_c', 'Liver function tests', 'Activated partial thromboplastin time', 'LFT→aPTT'],
];
foreach ($labSwaps as $swap) {
    [$field, $before, $after, $label] = $swap;
    $repaired = $monBase;
    $repaired[$field] = $after;
    $raw = [
        'repaired_question' => [
            'title' => $repaired['title'],
            'stem' => $repaired['stem'],
            'lead_in' => $repaired['lead_in'],
            'option_a' => $repaired['option_a'],
            'option_b' => $repaired['option_b'],
            'option_c' => $repaired['option_c'],
            'option_d' => $repaired['option_d'],
            'option_e' => $repaired['option_e'],
            'correct_answer_letter' => 'A',
            'correct_answer' => $repaired['correct_answer'],
            'justification' => $repaired['justification'],
        ],
        'change_log' => [
            [
                'field' => $field,
                'before' => $before,
                'after' => $after,
                'reason' => 'Wording',
                'change_type' => 'style',
                'risk' => 'low',
            ],
        ],
        'repair_risk_level' => 'low',
        'any_clinical_content_changed' => false,
        'repair_summary' => 'Lab swap.',
    ];
    $norm = normalizeSbaAiRepairResponse($raw, $monBase);
    repairTest(
        $norm['ok'] && in_array($norm['data']['change_log'][0]['change_type'] ?? '', ['educational_content', 'clinical'], true),
        'VTE monitoring ' . $label . ': not style'
    );
}

$copdOxygenDashRaw = [
    'repaired_question' => [
        'title' => $oxygenOriginal['title'],
        'stem' => $oxygenOriginal['stem'],
        'lead_in' => $oxygenOriginal['lead_in'],
        'option_a' => $oxygenOriginal['option_a'],
        'option_b' => 'Oxygen 4 L/min via Venturi mask at 28%',
        'option_c' => $oxygenOriginal['option_c'],
        'option_d' => $oxygenOriginal['option_d'],
        'option_e' => $oxygenOriginal['option_e'],
        'correct_answer_letter' => '',
        'correct_answer' => '—',
        'justification' => $oxygenOriginal['justification'],
    ],
    'change_log' => [
        [
            'field' => 'option_b',
            'before' => $oxygenOriginal['option_b'],
            'after' => 'Oxygen 4 L/min via Venturi mask at 28%',
            'reason' => 'Safer distractor',
            'change_type' => 'educational_content',
            'risk' => 'medium',
        ],
    ],
    'repair_risk_level' => 'medium',
    'any_clinical_content_changed' => true,
    'repair_summary' => 'COPD oxygen distractor update.',
];
$copdDashNorm = normalizeSbaAiRepairResponse($copdOxygenDashRaw, $oxygenOriginal);
repairTest($copdDashNorm['ok'] === true, 'COPD dash repair normalizes to blocked payload');
repairTest(
    $copdDashNorm['ok'] && !empty($copdDashNorm['data']['repair_blocked']),
    'COPD dash repair_blocked true'
);
repairTest(
    $copdDashNorm['ok'] && empty($copdDashNorm['data']['repaired_question']),
    'COPD dash no repaired_question preview'
);
$dashReasons = $copdDashNorm['data']['repair_blocked_reasons'] ?? [];
repairTest(
    $copdDashNorm['ok']
    && is_array($dashReasons)
    && $dashReasons !== []
    && str_contains((string) $dashReasons[0], 'incomplete'),
    'COPD dash blocked reason mentions incomplete'
);

$copdFixedRaw = $copdOxygenDashRaw;
$copdFixedRaw['repaired_question']['correct_answer_letter'] = 'A';
$copdFixedRaw['repaired_question']['correct_answer'] = $oxygenOriginal['option_a'];
$copdFixedNorm = normalizeSbaAiRepairResponse($copdFixedRaw, $oxygenOriginal);
repairTest(
    $copdFixedNorm['ok'] && empty($copdFixedNorm['data']['repair_blocked']),
    'COPD valid correct_answer still produces repair preview'
);
repairTest(
    $copdFixedNorm['ok']
    && ($copdFixedNorm['data']['repaired_question']['correct_answer'] ?? '') === $oxygenOriginal['option_a'],
    'COPD valid repair keeps keyed answer text'
);

echo PHP_EOL . '========== SUMMARY ==========' . PHP_EOL;
echo "Passed: {$passed} Failed: {$failed}" . PHP_EOL;
exit($failed > 0 ? 1 : 0);
