import java.time.*;
import java.util.*;

/** Run with assertions implemented explicitly: no -ea flag or test dependencies needed. */
public class MemoryDemoTest {
    static int passed;
    static final Instant T = MemoryDemo.NOW;
    static MemoryDemo.Store store() { return new MemoryDemo.Store(Clock.fixed(T, ZoneOffset.UTC)); }
    static void check(boolean value, String message) { if (!value) throw new AssertionError(message); }
    static void rejects(Runnable action) {
        try { action.run(); } catch (IllegalArgumentException expected) { return; }
        throw new AssertionError("expected rejection");
    }
    static void test(String name, Runnable action) { action.run(); passed++; System.out.println("PASS " + name); }
    public static void main(String[] args) {
        test("extract supported statement", () -> check(MemoryDemo.extract("我主要写 Java").orElseThrow().slot().equals("background.primary"), "slot"));
        test("ignore unsupported and negative statement", () -> {
            check(MemoryDemo.extract("今天下雨").isEmpty(), "unsupported");
            check(MemoryDemo.extract("我不主要写 Java").isEmpty(), "negative");
            check(MemoryDemo.extract("我最近在学习 Python？").isEmpty(), "question");
        });
        test("duplicate event is idempotent", () -> {
            var s = store(); var a = s.remember("alice", "e1", "我主要写 Java", T, null).orElseThrow();
            var b = s.remember("alice", "e1", "我主要写 Java", T, null).orElseThrow();
            check(a.id().equals(b.id()) && s.current("alice").size() == 1 && b.evidence().size() == 1, "duplicate");
        });
        test("event key cannot change payload", () -> {
            var s = store(); s.remember("alice", "e1", "我主要写 Java", T, null);
            rejects(() -> s.remember("alice", "e1", "我主要写 Python", T, null));
        });
        test("same fact merges independent evidence", () -> {
            var s = store(); s.remember("alice", "e1", "我主要写 Java", T.minusSeconds(10), null);
            var m = s.remember("alice", "e2", "我主要写 Java", T, null).orElseThrow();
            check(m.version() == 1 && m.evidence().size() == 2, "merge");
        });
        test("learning goal supplements background", () -> {
            var s = store(); s.remember("alice", "e1", "我主要写 Java", T, null);
            s.remember("alice", "e2", "我最近在学习 Python", T, null);
            check(s.current("alice").size() == 2, "supplement");
        });
        test("replacement closes prior validity window", () -> {
            var s = store(); s.remember("alice", "e1", "我主要写 Java", T.minusSeconds(10), null);
            var m = s.remember("alice", "e2", "我的主要语言改为 Kotlin", T, null).orElseThrow();
            var old = s.history("alice", "background.primary").getFirst();
            check(m.version() == 2 && old.validUntil().equals(T), "version");
            check(s.search("alice", "Java", 5).isEmpty() && s.current("alice").getFirst().value().equals("Kotlin"), "current");
        });
        test("half-open expiration boundary", () -> {
            var s = store(); s.remember("alice", "e1", "我最近在学习 Python", T.minusSeconds(10), T);
            check(s.current("alice").isEmpty(), "expired");
            check(s.history("alice", "goal.current").getFirst().activeAt(T.minusNanos(1)), "before end");
        });
        test("validity start is inclusive", () -> {
            var s = store(); var m = s.remember("alice", "e1", "我主要写 Java", T, null).orElseThrow();
            check(m.activeAt(T) && !m.activeAt(T.minusNanos(1)), "start boundary");
        });
        test("user and idempotency isolation", () -> {
            var s = store(); s.remember("alice", "e1", "我主要写 Java", T, null);
            s.remember("bob", "e1", "我主要写 Python", T, null);
            check(s.search("alice", "Python", 10).isEmpty() && s.search("bob", "Python", 10).size() == 1, "isolation");
        });
        test("forget erases all revisions and source quotes", () -> {
            var s = MemoryDemo.learningAssistant(); check(s.forget("alice", "background.primary") == 2, "all versions");
            check(s.history("alice", "background.primary").isEmpty() && s.search("alice", "Kotlin", 10).isEmpty(), "erase");
            check(s.current("bob").size() == 1, "other user remains");
        });
        test("old retry cannot resurrect erased memory", () -> {
            var s = store(); s.remember("alice", "e1", "我主要写 Java", T, null); s.forget("alice", "background.primary");
            check(s.remember("alice", "e1", "我主要写 Java", T, null).isEmpty() && s.current("alice").isEmpty(), "resurrection");
        });
        test("new event can explicitly remember again", () -> {
            var s = store(); s.remember("alice", "e1", "我主要写 Java", T.minusSeconds(10), null); s.forget("alice", "background.primary");
            check(s.remember("alice", "e2", "我主要写 Java", T, null).orElseThrow().version() == 2, "new intent");
        });
        test("unmatched and empty queries return no hits", () -> {
            var s = MemoryDemo.learningAssistant();
            check(s.search("alice", "Rust", 10).isEmpty() && s.search("alice", "", 10).isEmpty(), "no match");
        });
        test("stable score time and id order", () -> {
            var s = store(); s.remember("alice", "e1", "我主要写 Python", T, null);
            s.remember("alice", "e2", "我最近在学习 Python", T, null);
            var first = s.search("alice", "Python", 10);
            check(first.equals(s.search("alice", "Python", 10)) && first.getFirst().memory().id().equals("m-1"), "tie break");
        });
        test("Chinese bigrams and Latin case normalize", () -> {
            var s = MemoryDemo.learningAssistant();
            check(s.search("alice", "生成器", 10).size() == 1 && s.search("alice", "PYTHON", 10).size() == 2, "terms");
        });
        test("context budget includes labels and never cuts a fact", () -> {
            var s = MemoryDemo.learningAssistant(); String c = s.context("alice", "Python", 70);
            check(c.codePointCount(0, c.length()) <= 70 && c.contains("source=") && c.endsWith("\n"), "budget");
            check(s.context("alice", "Python", 1).isEmpty(), "small budget");
        });
        test("late event rejected without changing state", () -> {
            var s = store(); s.remember("alice", "e1", "我主要写 Java", T, null);
            rejects(() -> s.remember("alice", "e2", "我主要写 Python", T.minusSeconds(1), null));
            check(s.current("alice").getFirst().value().equals("Java"), "unchanged");
        });
        test("simultaneous conflicting update rejected", () -> {
            var s = store(); s.remember("alice", "e1", "我主要写 Java", T, null);
            rejects(() -> s.remember("alice", "e2", "我的主要语言改为 Python", T, null));
        });
        test("invalid times and limits rejected", () -> {
            var s = store();
            rejects(() -> s.remember("alice", "e1", "我主要写 Java", T.plusSeconds(1), null));
            rejects(() -> s.remember("alice", "e1", "我主要写 Java", T, T));
            rejects(() -> s.search("alice", "Java", -1));
            rejects(() -> s.context("alice", "Java", -1));
        });
        System.out.println("RESULT " + passed + "/20 passed; deterministic contracts, not LLM accuracy.");
    }
}
