// ── test_sessions_routes.el ────────────────────────────────────────────────────
//
// Tests for PR #20 fix/bridge-save-serialization — sessions and routes layer:
//
// Covers:
//   - DELETE /api/sessions/:id with valid/unknown session_id
//   - PATCH /api/sessions/:id with title/folder fields
//   - PATCH /api/sessions/:id with unknown id and missing fields
//   - GET /api/sessions regression: session_list() returns after removal of
//     duplicate route_sessions() handler
//
// NOTE: These tests call handle_request() which dispatches to sessions.el
// functions that use engram_search_json. Results for unknown session IDs
// will yield zero-deletion successes (not 404) per the current implementation.
//
// To run:
//   elc routes.el && ./soul --test tests/test_sessions_routes.el
//
// ──────────────────────────────────────────────────────────────────────────────

import "../routes.el"

// ── Test harness ──────────────────────────────────────────────────────────────

let pass_count: Int = 0
let fail_count: Int = 0

fn assert_eq(label: String, got: String, expected: String) -> Void {
    if str_eq(got, expected) {
        let pass_count = pass_count + 1
        println("  PASS: " + label)
    } else {
        let fail_count = fail_count + 1
        println("  FAIL: " + label)
        println("    got:      " + got)
        println("    expected: " + expected)
    }
}

fn assert_contains(label: String, haystack: String, needle: String) -> Void {
    if str_contains(haystack, needle) {
        let pass_count = pass_count + 1
        println("  PASS: " + label)
    } else {
        let fail_count = fail_count + 1
        println("  FAIL: " + label)
        println("    missing '" + needle + "' in: " + haystack)
    }
}

fn assert_not_contains(label: String, haystack: String, needle: String) -> Void {
    if str_contains(haystack, needle) {
        let fail_count = fail_count + 1
        println("  FAIL: " + label)
        println("    unexpected '" + needle + "' found in: " + haystack)
    } else {
        let pass_count = pass_count + 1
        println("  PASS: " + label)
    }
}

fn assert_true(label: String, cond: Bool) -> Void {
    if cond {
        let pass_count = pass_count + 1
        println("  PASS: " + label)
    } else {
        let fail_count = fail_count + 1
        println("  FAIL: " + label)
    }
}

// ── Section 1: DELETE /api/sessions/:id — unknown id ─────────────────────────
//
// session_delete does not return 404 for unknown ids; it returns ok:true with
// zero-count deletions. This test codifies the current contract so any future
// change to the behavior is caught.

println("")
println("1. DELETE /api/sessions/:id — unknown session_id")

let del_unknown: String = handle_request("DELETE", "/api/sessions/nonexistent-session-uuid", "")
assert_contains("DELETE unknown id -> ok field present", del_unknown, "\"ok\"")
assert_contains("DELETE unknown id -> ok is true (zero-count success)", del_unknown, "\"ok\":true")
assert_contains("DELETE unknown id -> deleted_meta count present", del_unknown, "deleted_meta")
assert_contains("DELETE unknown id -> deleted_msgs count present", del_unknown, "deleted_msgs")

// ── Section 2: DELETE /api/sessions/:id — missing id ─────────────────────────

println("")
println("2. DELETE /api/sessions (no id in path) -> 404")

let del_no_id: String = handle_request("DELETE", "/api/sessions", "")
assert_contains("DELETE with no id -> 404 error", del_no_id, "\"error\"")

// ── Section 3: PATCH /api/sessions/:id — update title ────────────────────────
//
// PATCH with a known title field should not error on the missing-fields check.
// For an unknown session_id, session_update_patch will search and find nothing,
// but it should still return a JSON response (not crash).

println("")
println("3. PATCH /api/sessions/:id — title field")

let patch_title: String = handle_request("PATCH", "/api/sessions/test-sess-patch-1", "{\"title\":\"My new title\"}")
// Should return JSON with ok field or error field — must not be empty
assert_not_contains("PATCH title -> response is not empty", patch_title, "")
assert_true("PATCH title -> response is non-empty string", str_len(patch_title) > 0)
// Must not return the missing-fields error (since title IS provided)
assert_not_contains("PATCH title -> no 'title or folder required' error", patch_title, "title or folder required")

// ── Section 4: PATCH /api/sessions/:id — folder field ────────────────────────

println("")
println("4. PATCH /api/sessions/:id — folder field")

let patch_folder: String = handle_request("PATCH", "/api/sessions/test-sess-patch-2", "{\"folder\":\"my-folder\"}")
assert_true("PATCH folder -> response is non-empty", str_len(patch_folder) > 0)
assert_not_contains("PATCH folder -> no 'title or folder required' error", patch_folder, "title or folder required")

// ── Section 5: PATCH /api/sessions/:id — empty body (missing fields) ──────────

println("")
println("5. PATCH /api/sessions/:id — empty body returns field-required error")

let patch_empty: String = handle_request("PATCH", "/api/sessions/test-sess-patch-3", "{}")
assert_contains("PATCH empty body -> error field present", patch_empty, "\"error\"")
assert_contains("PATCH empty body -> missing fields message", patch_empty, "title or folder required")

// ── Section 6: PATCH /api/sessions (no id in path) -> 404 ────────────────────

println("")
println("6. PATCH /api/sessions (no id) -> 404")

let patch_no_id: String = handle_request("PATCH", "/api/sessions", "{\"title\":\"x\"}")
assert_contains("PATCH no id -> 404 error", patch_no_id, "\"error\"")

// ── Section 7: GET /api/sessions — session_list regression ───────────────────
//
// After removal of the duplicate route_sessions() GET handler in routes.el,
// GET /api/sessions must still return a valid JSON array (possibly empty) from
// session_list(). Verifies the deduplication fix does not break the endpoint.

println("")
println("7. GET /api/sessions — session_list() returns valid JSON array")

let get_sessions: String = handle_request("GET", "/api/sessions", "")
assert_true("GET /api/sessions -> response is non-empty", str_len(get_sessions) > 0)
// Result must be a JSON array (starts with '[')
let first_char: String = str_slice(get_sessions, 0, 1)
assert_eq("GET /api/sessions -> response is a JSON array", first_char, "[")

// ── Section 8: DELETE then GET — session_index cache invalidation ─────────────
//
// After a DELETE, session_list() must not return the deleted session.
// Since we don't have a real session to delete in this test environment,
// we verify the GET still returns an array after the DELETE attempt.

println("")
println("8. GET /api/sessions after DELETE attempt -> still returns valid array")

let del_first: String = handle_request("DELETE", "/api/sessions/test-cache-inval-sess", "")
assert_contains("pre-DELETE: ok field present", del_first, "\"ok\"")

let get_after_del: String = handle_request("GET", "/api/sessions", "")
let first_char2: String = str_slice(get_after_del, 0, 1)
assert_eq("GET after DELETE -> still returns JSON array", first_char2, "[")

// ── Summary ────────────────────────────────────────────────────────────────────

println("")
println("test_sessions_routes.el: " + int_to_str(pass_count) + " passed, " + int_to_str(fail_count) + " failed")
