#!/usr/bin/env python
"""selftest for errorlog-dream: run the analyzer against a synthetic errorlog
in a temp dir and assert each detector fires, then assert --apply-archive
moves exactly the old entries and leaves the marker, recent entries, and
unparsed legacy blocks intact."""

import io
import os
import shutil
import sys
import tempfile
from datetime import datetime, timedelta, timezone

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import errorlog_dream as ed


def build_fixture(root, today):
    d = lambda n: (today - timedelta(days=n)).strftime("%Y-%m-%d")
    old1 = d(95)
    old2 = d(65)
    recent = d(3)
    lines = [
        "# Error Log",
        "",
        ed.MARKER,
        "",
        # noise cluster: same machine+skill+shape on 3 distinct days
        "%s | BOX-A | widget/api | HTTP 500 on id 12345 [ctx: cmd=list]" % d(1),
        "",
        "%s | BOX-A | widget/api | HTTP 500 on id 99881 [ctx: cmd=list] (x3)" % d(2),
        "",
        "%s | BOX-A | widget/api | HTTP 500 on id 40404 [ctx: cmd=list]" % recent,
        "",
        # repeat ref (2x) citing an existing memory
        "%s | BOX-B | bash/env | [friction] used /tmp again [ctx: ref=fix_tmp_rule]" % d(4),
        "",
        "%s | BOX-B | bash/env | [friction] used /tmp AGAIN again [ctx: ref=fix_tmp_rule]" % recent,
        "",
        # machine-name case drift
        "%s | box-b | coord | HTTP 0 talking to coord" % d(5),
        "",
        # resolved entry
        "%s | BOX-A | sync/submodules | checkout aborted. [RESOLVED %s] fixed in sync.sh" % (d(6), d(5)),
        "",
        # correction
        "%s | BOX-A | client/foo | [correction] assumed X; correct is Y" % d(7),
        "",
        # old entries -> archive candidates (two months)
        "%s | BOX-A | oldskill | ancient failure one" % old1,
        "",
        "%s | BOX-B | oldskill | ancient failure two" % old2,
        "",
        # legacy multi-line unparsed block (no pipes on first line)
        "Some legacy hand-written note",
        "spanning two lines with no format.",
        "",
    ]
    log = os.path.join(root, "errorlog.md")
    with io.open(log, "w", encoding="utf-8", newline="\n") as fh:
        fh.write("\n".join(lines))
    mem = os.path.join(root, ".claude", "memory")
    os.makedirs(mem)
    with io.open(os.path.join(mem, "fix_tmp_rule.md"), "w", encoding="utf-8") as fh:
        fh.write("---\nname: fix_tmp_rule\n---\nrule body\n")
    return log


def main():
    tmp = tempfile.mkdtemp(prefix="eldream-test-")
    failures = []
    ok = lambda cond, name: failures.append(name) if not cond else None
    try:
        today = datetime.now(timezone.utc)
        log = build_fixture(tmp, today)
        header, entries, unparsed = ed.parse_log(log)

        ok(len(entries) == 10, "parse: 10 entries (got %d)" % len(entries))
        ok(len(unparsed) == 1, "parse: 1 unparsed block")
        ok(any(e.count == 3 for e in entries), "parse: (x3) counter read")

        r = ed.analyze(entries, unparsed, tmp, 60, today)
        ok(r["weighted"] == 12, "weighted count incl x3 (got %d)" % r["weighted"])
        ok(r["by_type"].get("friction") == 2 and r["by_type"].get("correction") == 1,
           "type tally")
        ok(any(ref == "fix_tmp_rule" and n == 2 and exists
               for ref, n, exists, _ in r["repeat_refs"]),
           "repeat-ref detector (existing memory)")
        ok(any(skill == "widget/api" and w == 5 for _, skill, _, w, _ in r["noise"]),
           "noise-cluster detector")
        ok(len(r["resolved"]) == 1, "resolved detector")
        ok("box-b" in r["machine_drift"], "machine-drift detector")
        ok(len(r["archive"]) == 2, "archive candidates (got %d)" % len(r["archive"]))

        report = ed.render(r, 60)
        for token in ("STRENGTHEN?", "SUPPRESS?", "ARCHIVE?", "MACHINE-NAME DRIFT"):
            ok(token in report, "report contains %s" % token)

        # --apply-archive: moves the 2 old entries, keeps everything else
        rc = ed.main(["--log", log, "--root", tmp, "--no-file", "--apply-archive"])
        ok(rc == 0, "apply-archive exit 0")
        h2, e2, u2 = ed.parse_log(log)
        ok(len(e2) == 8, "post-archive: 8 entries kept (got %d)" % len(e2))
        ok(len(u2) == 1, "post-archive: unparsed block untouched")
        with io.open(log, encoding="utf-8") as fh:
            body = fh.read()
        ok(ed.MARKER in body, "post-archive: marker intact")
        ok("ancient failure" not in body, "post-archive: old entries removed")
        arch = os.path.join(tmp, "errorlog-archive")
        months = sorted(f for f in os.listdir(arch) if f.endswith(".md"))
        ok(len(months) == 2, "archive split by month (got %s)" % months)
        joined = ""
        for f in months:
            with io.open(os.path.join(arch, f), encoding="utf-8") as fh:
                joined += fh.read()
        ok("ancient failure one" in joined and "ancient failure two" in joined,
           "archived content present")

        # idempotent: second run archives nothing
        rc = ed.main(["--log", log, "--root", tmp, "--no-file", "--apply-archive"])
        _, e3, _ = ed.parse_log(log)
        ok(rc == 0 and len(e3) == 8, "second apply-archive is a no-op")
    finally:
        shutil.rmtree(tmp, ignore_errors=True)

    if failures:
        print("[ERROR] selftest FAILED:")
        for f in failures:
            print("  - " + f)
        return 1
    print("[OK] errorlog-dream selftest: all assertions passed")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
