<?php
/**
 * Bug Condition Exploration Test — Gap Date Returns Null Session and Term
 *
 * Validates: Requirements 1.1, 1.2, 1.3
 *
 * PURPOSE: Demonstrate the bug in portal/payschoolfees.php where the original
 * inline primary session query returns 0 rows during an inter-term gap (after
 * one term's end_date and before the next term's start_date). Because no
 * fallback exists, $currentSessionName and $currentTermName remain null.
 *
 * EXPECTED OUTCOME: This test FAILS on unfixed code — confirming the bug.
 * The primary query finds no active session during a gap date, so the
 * assertions that expect non-null values will fail.
 *
 * Run via CLI: php portal/tests/test_gap_bug_condition.php
 */

// ── Bootstrap ────────────────────────────────────────────────────────────────
$root = dirname(__DIR__); // portal/
require_once $root . '/includes/dbconnection.php';

if (!isset($con) || mysqli_connect_errno()) {
    echo "FATAL: Could not connect to database.\n";
    exit(1);
}

// ── Helpers ──────────────────────────────────────────────────────────────────
$pass = 0;
$fail = 0;
$counterexamples = [];

function assert_test(string $label, bool $condition, string $counterexample = ''): void
{
    global $pass, $fail, $counterexamples;
    if ($condition) {
        echo "[PASS] $label\n";
        $pass++;
    } else {
        echo "[FAIL] $label\n";
        if ($counterexample !== '') {
            echo "       Counterexample: $counterexample\n";
        }
        $fail++;
        $counterexamples[] = $counterexample ?: $label;
    }
}

// ── Setup: insert test rows simulating a gap date scenario ───────────────────
// The session and term both ended 2 days ago — today is in the gap.
$sessionName = 'TEST_GAP_SESSION_2025';
$termName = 'TEST_GAP_TERM_2025';

// Clean up any leftover rows from a previous run
$con->query("DELETE st FROM school_terms st
             INNER JOIN school_sessions ss ON st.session_id = ss.id
             WHERE ss.session_name = '$sessionName'");
$con->query("DELETE FROM school_sessions WHERE session_name = '$sessionName'");

// Insert session: started 30 days ago, ended 2 days ago (gap — not active today)
$insertSession = $con->prepare(
    "INSERT INTO school_sessions (session_name, start_date, end_date)
     VALUES (?, DATE_SUB(CURDATE(), INTERVAL 30 DAY), DATE_SUB(CURDATE(), INTERVAL 2 DAY))"
);
if (!$insertSession) {
    echo "FATAL: Could not prepare session insert: " . $con->error . "\n";
    exit(1);
}
$insertSession->bind_param('s', $sessionName);
$insertSession->execute();
$sessionId = $con->insert_id;
$insertSession->close();

if (!$sessionId) {
    echo "FATAL: Failed to insert test session row.\n";
    exit(1);
}

// Insert term linked to that session with the same date range
$insertTerm = $con->prepare(
    "INSERT INTO school_terms (term_name, session_id, start_date, end_date)
     VALUES (?, ?, DATE_SUB(CURDATE(), INTERVAL 30 DAY), DATE_SUB(CURDATE(), INTERVAL 2 DAY))"
);
if (!$insertTerm) {
    echo "FATAL: Could not prepare term insert: " . $con->error . "\n";
    exit(1);
}
$insertTerm->bind_param('si', $termName, $sessionId);
$insertTerm->execute();
$termId = $con->insert_id;
$insertTerm->close();

if (!$termId) {
    echo "FATAL: Failed to insert test term row.\n";
    exit(1);
}

echo "\n=== Bug Condition Exploration Test: Gap Date Returns Null Session and Term ===\n";
echo "Test session inserted: id=$sessionId, session_name='$sessionName'\n";
echo "Test term inserted:    id=$termId,    term_name='$termName'\n";
echo "Both rows: start_date = CURDATE()-30, end_date = CURDATE()-2\n";
echo "Today is in the GAP — the session/term ended 2 days ago.\n\n";

// ── Show current date for context ─────────────────────────────────────────────
$dateResult = $con->query("SELECT CURDATE() AS today");
$dateRow = $dateResult->fetch_assoc();
echo "MySQL CURDATE() = " . $dateRow['today'] . "\n\n";

// ── Test 1: Original inline primary session query (no fallback) ───────────────
// This is the EXACT query used in payschoolfees.php (and all 14 affected files).
// We scope it to the test session ID to isolate the gap scenario from any real
// active sessions in the DB. The test session ended 2 days ago — it is in a gap.
// During a gap date this returns 0 rows — $currentSessionName stays null.
$primarySessionQuery = "SELECT id, session_name FROM school_sessions
                        WHERE id = ?
                        AND start_date <= CURDATE() AND end_date >= CURDATE()
                        ORDER BY start_date ASC LIMIT 1";
$stmtPrimary = $con->prepare($primarySessionQuery);
$stmtPrimary->bind_param('i', $sessionId);
$stmtPrimary->execute();
$primarySessionResult = $stmtPrimary->get_result();
$sessionRow = ($primarySessionResult && $primarySessionResult->num_rows > 0)
    ? $primarySessionResult->fetch_assoc()
    : null;
$stmtPrimary->close();

// Simulate the original code: populate variables only if a row was found
$currentSessionId = 0;
$currentSessionName = null;
if ($sessionRow !== null) {
    $currentSessionId = $sessionRow['id'];
    $currentSessionName = $sessionRow['session_name'];
}

// ASSERTION: expects non-null — WILL FAIL on unfixed code (bug confirmed)
// The test session ended 2 days ago, so the primary query returns 0 rows.
// Without a fallback, $currentSessionName remains null.
assert_test(
    "Primary session query returns a session during gap date (Requirements 1.1)",
    $currentSessionName !== null,
    "primary query returned 0 rows for test session (end_date = CURDATE()-2). " .
    "\$currentSessionName = null. " .
    "No fallback exists in the original code — bug confirmed. " .
    "Requirements 1.1: session query returns no rows during inter-term gap."
);

// ── Test 2: Term query is never executed when session is null ─────────────────
// In the original code the term query is nested inside the session if-block.
// When $currentSessionName is null, the term query never runs.
$currentTermId = 0;
$currentTermName = null;

if ($currentSessionName !== null) {
    // This block is only reached when a session was found — skipped on gap dates
    $termQuery = "SELECT id, term_name FROM school_terms
                  WHERE session_id = ? AND start_date <= CURDATE() AND end_date >= CURDATE()
                  ORDER BY start_date ASC LIMIT 1";
    $stmtTerm = $con->prepare($termQuery);
    $stmtTerm->bind_param('i', $currentSessionId);
    $stmtTerm->execute();
    $termResult = $stmtTerm->get_result();
    $termRow = $termResult ? $termResult->fetch_assoc() : null;
    $stmtTerm->close();

    if ($termRow !== null) {
        $currentTermId = $termRow['id'];
        $currentTermName = $termRow['term_name'];
    }
}

// ASSERTION: expects non-null — WILL FAIL on unfixed code (bug confirmed)
assert_test(
    "Term is resolved (non-null) during gap date (Requirements 1.2)",
    $currentTermName !== null,
    "\$currentTermName = null because the term query was never executed " .
    "(session was not found). " .
    "Requirements 1.2: term query never executes when session is null."
);

// ── Test 3: Downstream logic is broken when both are null ─────────────────────
// Simulates what payschoolfees.php does with the null values downstream.
// saveFeeRecord() is called with null session/term — this is the broken output.
$downstreamSessionArg = $currentSessionName; // null on gap date
$downstreamTermArg = $currentTermName;    // null on gap date

$downstreamOk = ($downstreamSessionArg !== null && $downstreamTermArg !== null);

assert_test(
    "Downstream fee logic receives non-null session and term (Requirements 1.3)",
    $downstreamOk,
    "saveFeeRecord() called with session='" . var_export($downstreamSessionArg, true) .
    "', term='" . var_export($downstreamTermArg, true) . "'. " .
    "Fee calculations, payment lookups, and page rendering are broken. " .
    "Requirements 1.3: null session/term causes broken output for all students during gap."
);

// ── Test 4: fetchCurrentSessionTerm-style query also returns zero IDs ─────────
// Group E files (admin_track_submissions.php etc.) use a function that runs
// the same primary query. Verify it also returns zero IDs on a gap date.
// Scoped to the test session ID to isolate from real active sessions in the DB.
$fetchSessionQuery = "SELECT id FROM school_sessions
                      WHERE id = ?
                      AND start_date <= CURDATE() AND end_date >= CURDATE()
                      ORDER BY start_date ASC LIMIT 1";
$stmtFetchSession = $con->prepare($fetchSessionQuery);
$stmtFetchSession->bind_param('i', $sessionId);
$stmtFetchSession->execute();
$fetchSessionResult = $stmtFetchSession->get_result();
$fetchSessionRow = ($fetchSessionResult && $fetchSessionResult->num_rows > 0)
    ? $fetchSessionResult->fetch_assoc()
    : null;
$stmtFetchSession->close();

$fetchedSessionId = $fetchSessionRow ? (int) $fetchSessionRow['id'] : 0;
$fetchedTermId = 0;

if ($fetchedSessionId > 0) {
    $fetchTermQuery = "SELECT id FROM school_terms
                       WHERE session_id = ? AND start_date <= CURDATE() AND end_date >= CURDATE()
                       ORDER BY start_date ASC LIMIT 1";
    $stmtFetch = $con->prepare($fetchTermQuery);
    $stmtFetch->bind_param('i', $fetchedSessionId);
    $stmtFetch->execute();
    $fetchTermResult = $stmtFetch->get_result();
    $fetchTermRow = $fetchTermResult ? $fetchTermResult->fetch_assoc() : null;
    $stmtFetch->close();
    $fetchedTermId = $fetchTermRow ? (int) $fetchTermRow['id'] : 0;
}

// ASSERTION: expects non-zero IDs — WILL FAIL on unfixed code (bug confirmed)
assert_test(
    "fetchCurrentSessionTerm-style query returns non-zero session_id during gap date",
    $fetchedSessionId > 0,
    "fetchCurrentSessionTerm() returns session_id=0 on gap date (test session ended CURDATE()-2). " .
    "Group E files (admin_track_submissions.php etc.) are also broken."
);

assert_test(
    "fetchCurrentSessionTerm-style query returns non-zero term_id during gap date",
    $fetchedTermId > 0,
    "fetchCurrentSessionTerm() returns term_id=0 on gap date. " .
    "No fallback exists — both IDs are zero during inter-term gap."
);

// ── Cleanup ───────────────────────────────────────────────────────────────────
$con->query("DELETE FROM school_terms WHERE id = $termId");
$con->query("DELETE FROM school_sessions WHERE id = $sessionId");
echo "\nTest rows cleaned up.\n";

// ── Summary ───────────────────────────────────────────────────────────────────
echo "\n=== Results: $pass passed, $fail failed ===\n";

if ($fail > 0) {
    echo "\nFAIL — Bug confirmed. Counterexample(s):\n";
    foreach ($counterexamples as $i => $ce) {
        echo "  " . ($i + 1) . ". $ce\n";
    }
    echo "\nRoot cause: The primary session/term queries use\n";
    echo "  WHERE start_date <= CURDATE() AND end_date >= CURDATE()\n";
    echo "During an inter-term gap (end_date < CURDATE()), this returns 0 rows.\n";
    echo "No fallback branch exists, so \$currentSessionName and \$currentTermName\n";
    echo "remain null, breaking all downstream fee calculations and page rendering.\n";
    echo "\nFix: add a fallback query (ORDER BY end_date DESC LIMIT 1) when the\n";
    echo "primary query returns no rows (see design.md for full implementation).\n";
    exit(1);
} else {
    echo "\nPASS — All assertions passed (fix already applied or unexpected state).\n";
    exit(0);
}
