// ── tests/test_bell_safety.el ─────────────────────────────────────────────────
//
// Unit tests for the Hard Bell safety layer added in feat/connectors-soul.
// Covers the public API exposed by safety.el:
//   - safety_detect_bell_level:  'none' / 'soft' / 'hard'
//   - safety_classify_hard_bell: 'self_harm' / 'abuse'
//   - safety_normalize:          smart-quote -> ASCII apostrophe normalisation
//   - safety_augment_system:     system prompt passthrough / augmentation
//   - handle_safety_contact_post: validation + read-back
//
// El test convention: mutable pass_count / fail_count globals, assert_* helpers,
// println for each result.  Mirrors the style in existing safety tests.
// ──────────────────────────────────────────────────────────────────────────────

import "../safety.el"

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)
    }
}

// ── Section 1: safety_detect_bell_level — 'none' ─────────────────────────────

println("")
println("1. safety_detect_bell_level — neutral input -> 'none'")

let level_neutral: String = safety_detect_bell_level("What time is the meeting tomorrow?")
assert_eq("neutral question -> none", level_neutral, "none")

let level_positive: String = safety_detect_bell_level("I love sunny days")
assert_eq("positive statement -> none", level_positive, "none")

let level_empty: String = safety_detect_bell_level("")
assert_eq("empty string -> none", level_empty, "none")

// ── Section 2: safety_detect_bell_level — single soft phrase stays 'none' ────

println("")
println("2. safety_detect_bell_level — single soft phrase does not reach 'soft' (threshold=2)")

let level_one_soft: String = safety_detect_bell_level("I have been feeling stressed lately")
assert_eq("single soft phrase alone -> none", level_one_soft, "none")

// ── Section 3: safety_detect_bell_level — 'soft' with two+ soft phrases ──────

println("")
println("3. safety_detect_bell_level — two soft phrases -> 'soft'")

let level_two_soft: String = safety_detect_bell_level("I am feeling overwhelmed and really anxious about everything")
assert_eq("two soft phrases -> soft", level_two_soft, "soft")

let level_multi_soft: String = safety_detect_bell_level("So depressed and hopeless, nothing matters anymore")
assert_eq("multiple soft phrases -> soft", level_multi_soft, "soft")

// ── Section 4: safety_detect_bell_level — 'hard' (self_harm phrases) ─────────

println("")
println("4. safety_detect_bell_level — self_harm phrases -> 'hard'")

let level_kill_myself: String = safety_detect_bell_level("I want to kill myself")
assert_eq("'kill myself' -> hard", level_kill_myself, "hard")

let level_suicide: String = safety_detect_bell_level("I have been suicidal for weeks")
assert_eq("'suicidal' -> hard", level_suicide, "hard")

let level_want_die: String = safety_detect_bell_level("I want to die, nothing is worth it")
assert_eq("'want to die' -> hard", level_want_die, "hard")

let level_overdose: String = safety_detect_bell_level("I am thinking about an overdose")
assert_eq("'overdose' -> hard", level_overdose, "hard")

let level_self_harm: String = safety_detect_bell_level("My history of self-harm is back")
assert_eq("'self-harm' -> hard", level_self_harm, "hard")

let level_no_reason: String = safety_detect_bell_level("There is no reason to live")
assert_eq("'no reason to live' -> hard", level_no_reason, "hard")

// ── Section 5: safety_detect_bell_level — 'hard' (abuse phrases) ─────────────

println("")
println("5. safety_detect_bell_level — abuse phrases -> 'hard'")

let level_he_hit: String = safety_detect_bell_level("he hit me last night")
assert_eq("'he hit me' -> hard", level_he_hit, "hard")

let level_dv: String = safety_detect_bell_level("I am experiencing domestic violence")
assert_eq("'domestic violence' -> hard", level_dv, "hard")

let level_abused: String = safety_detect_bell_level("I am being abused")
assert_eq("'being abused' -> hard", level_abused, "hard")

// ── Section 6: safety_detect_bell_level — 'hard' (general hard phrases) ──────

println("")
println("6. safety_detect_bell_level — general hard phrases -> 'hard'")

let level_hurting_me: String = safety_detect_bell_level("someone is hurting me right now")
assert_eq("'hurting me' -> hard", level_hurting_me, "hard")

let level_being_hurt: String = safety_detect_bell_level("I am being hurt and need help")
assert_eq("'being hurt' -> hard", level_being_hurt, "hard")

// ── Section 7: safety_classify_hard_bell — abuse -> 'abuse' ──────────────────

println("")
println("7. safety_classify_hard_bell — abuse phrases route to 'abuse'")

let class_he_hit: String = safety_classify_hard_bell("he hit me yesterday")
assert_eq("'he hit me' classifies as abuse", class_he_hit, "abuse")

let class_dv: String = safety_classify_hard_bell("domestic violence in my home")
assert_eq("'domestic violence' classifies as abuse", class_dv, "abuse")

let class_abused: String = safety_classify_hard_bell("I'm being abused by my partner")
assert_eq("'being abused' classifies as abuse", class_abused, "abuse")

// ── Section 8: safety_classify_hard_bell — self_harm phrases ─────────────────

println("")
println("8. safety_classify_hard_bell — self_harm phrases route to 'self_harm'")

let class_kill: String = safety_classify_hard_bell("I want to kill myself")
assert_eq("'kill myself' classifies as self_harm", class_kill, "self_harm")

let class_suicide: String = safety_classify_hard_bell("I am suicidal")
assert_eq("'suicidal' classifies as self_harm", class_suicide, "self_harm")

let class_overdose: String = safety_classify_hard_bell("took too many pills")
assert_eq("'took too many' classifies as self_harm", class_overdose, "self_harm")

// ── Section 9: safety_classify_hard_bell — Track B threat-to-others ──────────
//
// LIVE SAFETY FIX (approved by Will + Tim, 2026-07-14): a threat toward ANOTHER
// person ("going to kill/hurt <someone>") must classify as 'threat_other' and be
// routed to a refusal + 911 — NOT to the 'self_harm'/988 path. This section used
// to assert the old (buggy) fall-through to 'self_harm'; it now pins the fix.

println("")
println("9. safety_classify_hard_bell — threat-to-others routes to 'threat_other' (not self_harm)")

let class_going_kill: String = safety_classify_hard_bell("I am going to kill him tonight")
assert_eq("'going to kill him' classifies as threat_other", class_going_kill, "threat_other")

let class_going_hurt: String = safety_classify_hard_bell("I'm going to hurt them so badly")
assert_eq("'going to hurt them' classifies as threat_other", class_going_hurt, "threat_other")

let class_shoot: String = safety_classify_hard_bell("I'm going to shoot up the place")
assert_eq("'going to shoot' classifies as threat_other", class_shoot, "threat_other")

// Track A must still win over Track B on victim / self-directed phrasings:
let class_kill_me: String = safety_classify_hard_bell("he is going to kill me")
assert_eq("'going to kill me' stays abuse (Track A precedence)", class_kill_me, "abuse")

let class_kill_self: String = safety_classify_hard_bell("I am going to kill myself")
assert_eq("'kill myself' stays self_harm (Track A precedence)", class_kill_self, "self_harm")

// ── Section 10: safety_normalize — curly apostrophe normalisation ─────────────

println("")
println("10. safety_normalize — curly apostrophe normalisation")

// U+2019 RIGHT SINGLE QUOTATION MARK (UTF-8: \xe2\x80\x99) must become ASCII '
let smart_msg: String = "I can" + "\xe2\x80\x99" + "t go on anymore"
let normalized: String = safety_normalize(smart_msg)
assert_contains("smart-quote normalized to ASCII apostrophe", normalized, "can't go on")

// After normalisation, detect_bell_level must fire 'hard' on the smart-quote variant
let level_smart: String = safety_detect_bell_level(smart_msg)
assert_eq("smart-quote 'can't go on' -> hard (after normalize)", level_smart, "hard")

// ── Section 11: safety_augment_system — passthrough on neutral ───────────────

println("")
println("11. safety_augment_system — neutral input returns system unchanged")

let base_sys: String = "You are a helpful assistant."
let aug_neutral: String = safety_augment_system(base_sys, "What is the weather?")
assert_eq("neutral message -> system unchanged", aug_neutral, base_sys)

// ── Section 12: safety_augment_system — soft bell injects directive ──────────

println("")
println("12. safety_augment_system — soft bell injects soft directive")

let aug_soft: String = safety_augment_system(base_sys, "Feeling so overwhelmed and completely anxious")
assert_contains("soft augment -> contains original system", aug_soft, base_sys)
assert_contains("soft augment -> contains SUBSTRATE DIRECTIVE", aug_soft, "SUBSTRATE DIRECTIVE")
assert_contains("soft augment -> contains soft care text", aug_soft, "genuine care")

// ── Section 13: safety_augment_system — hard self_harm injects 988 ───────────

println("")
println("13. safety_augment_system — hard self_harm injects crisis resources with 988")

let aug_hard: String = safety_augment_system(base_sys, "I want to kill myself tonight")
assert_contains("hard self_harm -> contains SUBSTRATE DIRECTIVE", aug_hard, "SUBSTRATE DIRECTIVE")
assert_contains("hard self_harm -> includes 988 crisis line", aug_hard, "988")
assert_not_contains("hard self_harm -> no DV hotline (wrong routing)", aug_hard, "1-800-799-7233")

// ── Section 14: safety_augment_system — hard abuse routes to abuse directive ──

println("")
println("14. safety_augment_system — hard abuse injects abuse-specific directive")

let aug_abuse: String = safety_augment_system(base_sys, "he hit me and I am afraid of him")
assert_contains("hard abuse -> DV hotline present", aug_abuse, "1-800-799-7233")
assert_contains("hard abuse -> mentions not notifying contact", aug_abuse, "safety contact")

// ── Section 14b: safety_augment_system — Track B threat-to-others routing ─────
//
// LIVE SAFETY FIX (approved by Will + Tim, 2026-07-14): a homicide/assault threat
// must be routed to a refusal + 911, and must NOT surface the 988 suicide line
// or pull in the safety contact.

println("")
println("14b. safety_augment_system — threat-to-others injects refusal + 911, never 988/contact")

let aug_threat: String = safety_augment_system(base_sys, "I am going to kill him tonight")
assert_contains("threat_other -> contains SUBSTRATE DIRECTIVE", aug_threat, "SUBSTRATE DIRECTIVE")
assert_contains("threat_other -> directs to 911", aug_threat, "911")
assert_contains("threat_other -> refuses to help harm another", aug_threat, "harming another person")
assert_not_contains("threat_other -> NO 988 suicide line", aug_threat, "988")
assert_not_contains("threat_other -> NO safety-contact involvement", aug_threat, "safety contact")
assert_not_contains("threat_other -> NO 'are you safe right now' victim frame", aug_threat, "are you safe right now")

// Detection must still fire hard on a weapon phrase not present in general_hard:
let level_shoot: String = safety_detect_bell_level("I'm going to shoot up the office")
assert_eq("'going to shoot' -> hard", level_shoot, "hard")

// ── Section 15: handle_safety_contact_post — validation ───────────────────────

println("")
println("15. handle_safety_contact_post — non-crisis without name returns error")

let no_name_body: String = "{\"is_crisis_line\":false,\"contact_method\":\"phone\",\"contact_value\":\"555-1234\",\"relationship\":\"friend\"}"
let no_name_result: String = handle_safety_contact_post(no_name_body)
let no_name_ok: String = json_get(no_name_result, "ok")
let no_name_err: String = json_get(no_name_result, "error")
assert_eq("no name -> ok==false", no_name_ok, "false")
assert_eq("no name -> error is 'name is required'", no_name_err, "name is required")

// ── Section 16: handle_safety_contact_post — write then read back ──────────────

println("")
println("16. handle_safety_contact_post — write then read back verifies persistence")

let contact_body: String = "{\"is_crisis_line\":false,\"name\":\"Test Contact\",\"contact_method\":\"phone\",\"contact_value\":\"555-9876\",\"relationship\":\"sibling\"}"
let write_result: String = handle_safety_contact_post(contact_body)
let write_ok: String = json_get(write_result, "ok")
assert_eq("contact write -> ok==true", write_ok, "true")
assert_contains("contact write -> result has configured", write_result, "\"configured\"")
assert_contains("contact write -> result has name", write_result, "Test Contact")

let read_result: String = handle_safety_contact_get()
assert_eq("contact read-back -> configured==true", json_get(read_result, "configured"), "true")
assert_contains("contact read-back -> name matches", read_result, "Test Contact")

// ── Section 17: handle_safety_contact_post — crisis line auto-fills ───────────

println("")
println("17. handle_safety_contact_post — crisis line auto-fills name and value")

let crisis_body: String = "{\"is_crisis_line\":true}"
let crisis_result: String = handle_safety_contact_post(crisis_body)
let crisis_ok: String = json_get(crisis_result, "ok")
assert_eq("crisis line write -> ok==true", crisis_ok, "true")
assert_contains("crisis line -> name is Crisis Line", crisis_result, "Crisis Line")
assert_contains("crisis line -> value is 988", crisis_result, "988")

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

println("")
println("bell_safety tests: " + int_to_str(pass_count) + " passed, " + int_to_str(fail_count) + " failed")
