<?php

/**
 * Form Save Round-Trip Test
 *
 * TDD: verifies that every field the form collects is persisted and can be
 * read back from the database after create and edit operations.
 *
 * Covered fields:
 *   - titre, subtitle, synopsis, année
 *   - orientation, ap, finality
 *   - languages (checkboxes), language_autre (free-text)
 *   - formats (checkboxes)
 *   - jury (promoteur, lecteur interne, lecteur externe)
 *   - tags / keywords
 *   - lien (baiu_link), license_id, access_type_id
 *   - objet
 *   - context_note
 *   - remarks, jury_points, exemplaire_baiu, exemplaire_erg, cc2r  (backoffice)
 *   - is_published
 */

putenv('DB_ENV=test');

if (!defined('APP_ROOT')) {
    define('APP_ROOT', dirname(__DIR__, 2));
}
if (!defined('STORAGE_ROOT')) {
    define('STORAGE_ROOT', APP_ROOT . '/storage');
}

require_once APP_ROOT . '/src/Database.php';
require_once APP_ROOT . '/src/Controllers/ThesisCreateController.php';
require_once APP_ROOT . '/src/Controllers/ThesisEditController.php';
require_once APP_ROOT . '/src/DuplicateThesisException.php';

// ── Helpers ───────────────────────────────────────────────────────────────────

/**
 * Build a minimal-but-complete POST payload.
 * Accepts overrides to test specific fields.
 */
function buildPost(Database $db, array $overrides = []): array
{
    $orientations  = $db->getAllOrientations();
    $apPrograms    = $db->getAllAPPrograms();
    $finalityTypes = $db->getAllFinalityTypes();
    $languages     = $db->getAllLanguages();
    $formatTypes   = $db->getAllFormatTypes();
    $licenseTypes  = $db->getAllLicenseTypes();

    if (empty($orientations) || empty($apPrograms) || empty($finalityTypes)
        || empty($languages) || empty($formatTypes) || empty($licenseTypes)) {
        throw new RuntimeException('Lookup tables empty — cannot build POST fixture');
    }

    $base = [
        'titre'                  => 'Test TFE Title',
        'subtitle'               => 'Test Subtitle',
        'auteurice'              => 'Doe, Jane',
        'mail'                   => 'jane@example.com',
        'synopsis'               => 'A short synopsis for testing purposes.',
        'année'                  => '2025',
        'orientation'            => (string)$orientations[0]['id'],
        'ap'                     => (string)$apPrograms[0]['id'],
        'finality'               => (string)$finalityTypes[0]['id'],
        'has_annexes'            => '',
        'languages'              => [(string)$languages[0]['id']],
        'language_autre'         => '',
        'formats'                => [(string)$formatTypes[0]['id']],
        'tag'                    => 'art, test, recherche',
        'jury_promoteur'         => 'Prof. Smith',
        'jury_lecteur_interne'   => ['Dr. Internal'],
        'jury_lecteur_externe'   => ['Dr. External'],
        'license_id'             => (string)$licenseTypes[0]['id'],
        'license_custom'         => '',
        'access_type_id'         => '2',
        'objet'                  => 'tfe',
        'lien'                   => '',
        'context_note'           => '',
        'remarks'                => '',
        'jury_points'            => '',
        'exemplaire_baiu'        => '',
        'exemplaire_erg'         => '',
        'cc2r'                   => '',
        'is_published'           => '',
    ];

    return array_merge($base, $overrides);
}

/**
 * Assert helper — throws on failure, echoes on pass.
 */
function assertEq(mixed $expected, mixed $actual, string $label): void
{
    if ($expected == $actual) {
        echo "  ✓ $label\n";
    } else {
        $e = var_export($expected, true);
        $a = var_export($actual, true);
        throw new RuntimeException("FAIL $label\n    expected: $e\n    actual:   $a");
    }
}

function assertContains(mixed $needle, array $haystack, string $label): void
{
    if (in_array($needle, $haystack, false)) {
        echo "  ✓ $label\n";
    } else {
        $a = implode(', ', $haystack);
        throw new RuntimeException("FAIL $label: $needle not in [$a]");
    }
}

function assertNotEmpty(mixed $value, string $label): void
{
    if (!empty($value)) {
        echo "  ✓ $label\n";
    } else {
        throw new RuntimeException("FAIL $label: value is empty");
    }
}

// ── Test setup ────────────────────────────────────────────────────────────────

echo "Form Save Round-Trip Test\n";
echo "=========================\n\n";

$db         = Database::getInstance();
$createCtrl = new ThesisCreateController($db);
$editCtrl   = new ThesisEditController($db);

$createdIds = [];

try {

    // =========================================================================
    // TEST 1: Create — basic fields persisted
    // =========================================================================
    echo "Test 1: Create — basic fields persisted\n";
    $post = buildPost($db, [
        'titre'    => 'Round-trip test titre',
        'subtitle' => 'Round-trip subtitle',
        'synopsis' => 'Round-trip synopsis',
        'année'    => '2025',
    ]);

    $thesisId = $createCtrl->submit($post, []);
    $createdIds[] = $thesisId;
    $row = $db->getThesis($thesisId);

    assertEq('Round-trip test titre',  $row['title'],    'title saved');
    assertEq('Round-trip subtitle',    $row['subtitle'], 'subtitle saved');
    assertEq('Round-trip synopsis',    $row['synopsis'], 'synopsis saved');
    assertEq(2025,                     (int)$row['year'], 'year saved');
    echo "\n";

    // =========================================================================
    // TEST 2: Create — language_autre creates and links new language
    // =========================================================================
    echo "Test 2: Create — language_autre creates and links new language\n";
    $uniqueLang = 'TestLang_' . bin2hex(random_bytes(4));
    $post = buildPost($db, [
        'titre'          => 'Language autre test',
        'languages'      => [],   // no checkbox
        'language_autre' => $uniqueLang,
    ]);

    $thesisId = $createCtrl->submit($post, []);
    $createdIds[] = $thesisId;

    $langIds  = $db->getThesisLanguageIds($thesisId);
    $allLangs = $db->getAllLanguages();
    $found    = array_filter($allLangs, fn($l) => $l['name'] === $uniqueLang);
    assertNotEmpty($found, "language '$uniqueLang' created in languages table");

    $createdLangId = (int)array_values($found)[0]['id'];
    assertContains((string)$createdLangId, array_map('strval', $langIds),
        'language_autre ID linked to thesis');
    echo "\n";

    // =========================================================================
    // TEST 3: Create — language_autre + checkbox together
    // =========================================================================
    echo "Test 5: Create — language_autre appended alongside checked languages\n";
    $db2      = Database::getInstance();
    $allLangs = $db2->getAllLanguages();
    $uniqueLang2 = 'TestLang2_' . bin2hex(random_bytes(4));
    $post = buildPost($db, [
        'titre'          => 'Language combo test',
        'languages'      => [(string)$allLangs[0]['id']],
        'language_autre' => $uniqueLang2,
    ]);

    $thesisId = $createCtrl->submit($post, []);
    $createdIds[] = $thesisId;
    $langIds = $db->getThesisLanguageIds($thesisId);

    assertContains((string)$allLangs[0]['id'], array_map('strval', $langIds),
        'checkbox language linked');

    $found2       = array_filter($db->getAllLanguages(), fn($l) => $l['name'] === $uniqueLang2);
    $createdLang2 = (int)array_values($found2)[0]['id'];
    assertContains((string)$createdLang2, array_map('strval', $langIds),
        'language_autre also linked');
    echo "\n";

    // =========================================================================
    // TEST 4: Edit — language checkboxes round-trip
    // =========================================================================
    echo "Test 3: Edit — language checkboxes round-trip\n";
    $allLangs = $db->getAllLanguages();
    $lang1    = (string)$allLangs[0]['id'];
    $lang2    = (string)$allLangs[1]['id'];

    $post = buildPost($db, [
        'titre'     => 'Lang checkbox test',
        'languages' => [$lang1],
    ]);
    $thesisId = $createCtrl->submit($post, []);
    $createdIds[] = $thesisId;

    $editPost = buildPost($db, [
        'titre'     => 'Lang checkbox test',
        'languages' => [$lang1, $lang2],
    ]);
    $editCtrl->save($thesisId, $editPost, []);
    $langIds = $db->getThesisLanguageIds($thesisId);

    assertContains($lang1, array_map('strval', $langIds), 'first language retained on edit');
    assertContains($lang2, array_map('strval', $langIds), 'second language added on edit');
    echo "\n";

    // =========================================================================
    // TEST 5: Edit — language_autre adds new language
    // =========================================================================
    echo "Test 4: Edit — language_autre creates and links on edit\n";
    $uniqueLang3 = 'EditLang_' . bin2hex(random_bytes(4));
    $editPost = buildPost($db, [
        'titre'          => 'Lang checkbox test',
        'languages'      => [$lang1],
        'language_autre' => $uniqueLang3,
    ]);
    $editCtrl->save($thesisId, $editPost, []);

    $langIds = $db->getThesisLanguageIds($thesisId);
    $found3  = array_filter($db->getAllLanguages(), fn($l) => $l['name'] === $uniqueLang3);
    assertNotEmpty($found3, "language '$uniqueLang3' created on edit");
    $createdLang3 = (int)array_values($found3)[0]['id'];
    assertContains((string)$createdLang3, array_map('strval', $langIds),
        'language_autre linked on edit');
    echo "\n";

    // =========================================================================
    // TEST 6: Create — backoffice fields persisted
    // =========================================================================
    echo "Test 5: Create — backoffice fields (remarks, jury_points, exemplaires, cc2r)\n";
    $post = buildPost($db, [
        'titre'           => 'Backoffice fields test',
        'remarks'         => 'Internal note here',
        'jury_points'     => '15.5',
        'exemplaire_baiu' => '1',
        'exemplaire_erg'  => '1',
        'cc2r'            => '1',
    ]);

    $thesisId = $createCtrl->submit($post, []);
    $createdIds[] = $thesisId;
    $raw = $db->getThesisRawFields($thesisId);

    assertEq('Internal note here', $raw['remarks'],         'remarks saved');
    assertEq(15.5,   (float)$raw['jury_points'],            'jury_points saved');
    assertEq(1,      (int)$raw['exemplaire_baiu'],          'exemplaire_baiu saved');
    assertEq(1,      (int)$raw['exemplaire_erg'],           'exemplaire_erg saved');
    assertEq(1,      (int)$raw['cc2r'],                     'cc2r saved');
    echo "\n";

    // =========================================================================
    // TEST 7: Edit — backoffice fields updated
    // =========================================================================
    echo "Test 6: Edit — backoffice fields updated\n";
    $editPost = buildPost($db, [
        'titre'           => 'Backoffice fields test',
        'remarks'         => 'Updated note',
        'jury_points'     => '18',
        'exemplaire_baiu' => '',
        'exemplaire_erg'  => '1',
        'cc2r'            => '',
    ]);
    $editCtrl->save($thesisId, $editPost, []);
    $raw = $db->getThesisRawFields($thesisId);

    assertEq('Updated note', $raw['remarks'],   'remarks updated');
    assertEq(18.0,  (float)$raw['jury_points'], 'jury_points updated');
    assertEq(0,     (int)$raw['exemplaire_baiu'], 'exemplaire_baiu cleared');
    assertEq(1,     (int)$raw['exemplaire_erg'],  'exemplaire_erg retained');
    assertEq(0,     (int)$raw['cc2r'],            'cc2r cleared');
    echo "\n";

    // =========================================================================
    // TEST 8: getOrCreateLanguage — idempotent
    // =========================================================================
    echo "Test 7: getOrCreateLanguage — idempotent (same name returns same ID)\n";
    $uniqueName = 'Idempotent_' . bin2hex(random_bytes(4));
    $id1 = $db->getOrCreateLanguage($uniqueName);
    $id2 = $db->getOrCreateLanguage($uniqueName);
    $id3 = $db->getOrCreateLanguage(strtolower($uniqueName)); // case-insensitive

    assertEq($id1, $id2, 'same ID on second call');
    assertEq($id1, $id3, 'same ID with different case');
    echo "\n";

    // =========================================================================
    // TEST 9: Edit — context_note saved
    // =========================================================================
    echo "Test 8: Edit — context_note saved\n";
    $post = buildPost($db, ['titre' => 'Context note test']);
    $thesisId = $createCtrl->submit($post, []);
    $createdIds[] = $thesisId;

    $editPost = buildPost($db, [
        'titre'        => 'Context note test',
        'context_note' => 'A contextual note visible publicly.',
    ]);
    $editCtrl->save($thesisId, $editPost, []);
    $raw = $db->getThesisRawFields($thesisId);

    assertEq('A contextual note visible publicly.', $raw['context_note'], 'context_note saved');
    echo "\n";

    echo "✅ All form save tests passed!\n";
    $result = true;

} catch (Exception $e) {
    echo '❌ FAIL: ' . $e->getMessage() . "\n";
    $result = false;
} finally {
    // Clean up test theses
    foreach ($createdIds as $id) {
        try { $db->deleteThesis($id); } catch (Exception $e) { /* ignore */ }
    }
    // Clean up test languages
    $allLangs = $db->getAllLanguages();
    foreach ($allLangs as $lang) {
        if (str_starts_with($lang['name'], 'TestLang_')
            || str_starts_with($lang['name'], 'TestLang2_')
            || str_starts_with($lang['name'], 'EditLang_')
            || str_starts_with($lang['name'], 'Idempotent_')) {
            try {
                $db->getConnection()->prepare('DELETE FROM languages WHERE id = ?')->execute([$lang['id']]);
            } catch (Exception $e) { /* ignore */ }
        }
    }
}

return $result ?? false;
