# SPDX-License-Identifier: AGPL-3.0-only
# Copyright (c) 2026 sol pbc

"""Tests for the awareness system."""

import json
import re

import pytest


@pytest.fixture(autouse=True)
def _temp_journal(monkeypatch, tmp_path):
    """Isolate all tests to a temporary journal."""
    monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path))


def _read_identity_history(journal_path):
    path = journal_path / "identity" / "history.jsonl"
    return [json.loads(line) for line in path.read_text().splitlines()]


def _assert_identity_history(record, *, file_name, actor, op, section, reason):
    assert list(record) == [
        "ts",
        "file",
        "actor",
        "op",
        "section",
        "reason",
        "before_hash",
        "after_hash",
        "bytes_before",
        "bytes_after",
    ]
    assert re.fullmatch(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z", record["ts"])
    assert record["file"] == file_name
    assert record["actor"] == actor
    assert record["op"] == op
    assert record["section"] == section
    assert record["reason"] == reason


class TestCurrentState:
    def test_empty_state_returns_empty_dict(self):
        from solstone.think.awareness import get_current

        assert get_current() == {}

    def test_update_state_creates_section(self):
        from solstone.think.awareness import get_current, update_state

        update_state("onboarding", {"path": "a", "status": "observing"})

        state = get_current()
        assert state["onboarding"]["path"] == "a"
        assert state["onboarding"]["status"] == "observing"

    def test_update_state_merges_into_existing(self):
        from solstone.think.awareness import get_current, update_state

        update_state("onboarding", {"path": "a", "status": "observing"})
        update_state("onboarding", {"observation_count": 5})

        state = get_current()
        assert state["onboarding"]["path"] == "a"
        assert state["onboarding"]["observation_count"] == 5

    def test_update_state_multiple_sections(self):
        from solstone.think.awareness import get_current, update_state

        update_state("onboarding", {"status": "complete"})
        update_state("preferences", {"nudge_frequency": "low"})

        state = get_current()
        assert state["onboarding"]["status"] == "complete"
        assert state["preferences"]["nudge_frequency"] == "low"

    def test_current_json_written_atomically(self, tmp_path):
        from solstone.think.awareness import _awareness_dir, update_state

        update_state("test", {"key": "value"})

        path = _awareness_dir() / "current.json"
        assert path.exists()
        data = json.loads(path.read_text())
        assert data["test"]["key"] == "value"


class TestDailyLog:
    def test_append_log_creates_file(self, tmp_path):
        from solstone.think.awareness import _awareness_dir, _today, append_log

        entry = append_log("state", key="test.started", message="café")

        log_path = _awareness_dir() / f"{_today()}.jsonl"
        assert log_path.exists()
        assert entry["kind"] == "state"
        assert entry["key"] == "test.started"
        assert entry["message"] == "café"
        assert "ts" in entry
        raw = log_path.read_bytes()
        assert b"caf\\u00e9" in raw
        assert b"caf\xc3\xa9" not in raw
        assert raw.endswith(b"\n")
        assert raw.count(b"\n") == 1

    def test_append_log_appends_multiple(self):
        from solstone.think.awareness import _today, append_log, read_log

        append_log("state", key="a")
        append_log("observation", message="saw something")
        append_log("nudge", message="hey")

        entries = read_log(_today())
        assert len(entries) == 3
        assert entries[0]["kind"] == "state"
        assert entries[1]["kind"] == "observation"
        assert entries[2]["kind"] == "nudge"

    def test_read_log_empty_returns_empty_list(self):
        from solstone.think.awareness import read_log

        assert read_log("20990101") == []

    def test_append_log_with_data(self):
        from solstone.think.awareness import _today, append_log, read_log

        append_log("observation", data={"meetings": 2, "entities": ["Alice"]})

        entries = read_log(_today())
        assert entries[0]["data"]["meetings"] == 2

    def test_append_log_with_extra_fields(self):
        from solstone.think.awareness import _today, append_log, read_log

        append_log("observation", segment="123456_300", detail="meeting detected")

        entries = read_log(_today())
        assert entries[0]["segment"] == "123456_300"
        assert entries[0]["detail"] == "meeting detected"


class TestImportTracking:
    def test_record_import_persists_state(self):
        from solstone.think.awareness import get_imports, record_import

        record_import("chatgpt")

        imports = get_imports()
        assert imports["has_imported"] is True
        assert imports["import_count"] == 1
        assert imports["sources_used"] == ["chatgpt"]
        assert imports["offer_declined"] is None
        assert imports["last_nudge"] is None

    def test_record_import_offer_declined_persists_state(self):
        from solstone.think.awareness import (
            get_imports,
            record_import_offer_declined,
        )

        record_import_offer_declined()

        imports = get_imports()
        assert imports["has_imported"] is False
        assert imports["import_count"] == 0
        assert imports["sources_used"] == []
        assert re.fullmatch(r"\d{8}T\d{2}:\d{2}:\d{2}", imports["offer_declined"])
        assert imports["last_nudge"] is None

    def test_record_import_nudge_persists_state(self):
        from solstone.think.awareness import get_imports, record_import_nudge

        record_import_nudge()

        imports = get_imports()
        assert imports["has_imported"] is False
        assert imports["import_count"] == 0
        assert imports["sources_used"] == []
        assert imports["offer_declined"] is None
        assert re.fullmatch(r"\d{8}T\d{2}:\d{2}:\d{2}", imports["last_nudge"])


class TestJournalState:
    def test_first_daily_ready_via_update_state(self):
        from solstone.think.awareness import get_current, update_state

        update_state(
            "journal",
            {"first_daily_ready": True, "first_daily_ready_at": "20260308T14:00:00"},
        )

        state = get_current()
        assert state["journal"]["first_daily_ready"] is True
        assert state["journal"]["first_daily_ready_at"] == "20260308T14:00:00"


class TestEnsureIdentityDirectory:
    """Tests for ensure_identity_directory()."""

    def test_creates_default_templates(self, tmp_path):
        from solstone.think.identity import ensure_identity_directory

        identity_dir = ensure_identity_directory()
        assert identity_dir == tmp_path / "identity"
        assert not (identity_dir / ("self" + ".md")).exists()
        assert not (identity_dir / "agency.md").exists()

        assert not (identity_dir / "digest.md").exists()
        assert (identity_dir / "health.md").exists()

    def test_idempotent_does_not_overwrite(self, tmp_path):
        from solstone.think.identity import ensure_identity_directory

        identity_dir = ensure_identity_directory()
        partner_path = identity_dir / "partner.md"
        partner_path.write_text("custom content", encoding="utf-8")

        # Call again — should NOT overwrite
        ensure_identity_directory()
        assert partner_path.read_text() == "custom content"

    def test_creates_partner_md(self, tmp_path):
        from solstone.think.identity import ensure_identity_directory

        identity_dir = ensure_identity_directory()
        partner_path = identity_dir / "partner.md"
        assert partner_path.exists()
        content = partner_path.read_text()
        assert "# partner" in content
        assert "## work patterns" in content
        assert "## communication style" in content
        assert "## relationship priorities" in content
        assert "## decision style" in content
        assert "## expertise domains" in content

    def test_does_not_overwrite_existing_partner_md(self, tmp_path):
        from solstone.think.identity import ensure_identity_directory

        identity_dir = tmp_path / "identity"
        identity_dir.mkdir()
        custom = "# partner\n\n## work patterns\nCustom content.\n"
        (identity_dir / "partner.md").write_text(custom)

        ensure_identity_directory()
        assert (identity_dir / "partner.md").read_text() == custom


class TestUpdateIdentitySection:
    """Tests for update_identity_section generic helper."""

    def test_update_partner_section(self, tmp_path):
        from solstone.think.identity import update_identity_section

        partner_md = "# partner\n\n## work patterns\n[observing]\n\n## communication style\n[observing]\n"
        (tmp_path / "identity").mkdir(exist_ok=True)
        (tmp_path / "identity" / "partner.md").write_text(partner_md)

        result = update_identity_section(
            "partner.md",
            "work patterns",
            "Prefers mornings",
            actor="test update identity section",
            reason="test",
        )
        assert result is True

        content = (tmp_path / "identity" / "partner.md").read_text()
        assert "Prefers mornings" in content
        assert "## communication style" in content
        assert "[observing]" in content  # other section preserved

    def test_update_nonexistent_file_returns_false(self, tmp_path):
        from solstone.think.identity import update_identity_section

        (tmp_path / "identity").mkdir(exist_ok=True)
        result = update_identity_section(
            "nonexistent.md",
            "heading",
            "content",
            actor="test update identity section",
            reason="test",
        )
        assert result is False

    def test_partner_update_prunes_getting_started(self, tmp_path):
        from solstone.think.identity import update_identity_section

        partner_md = (
            "# partner\n\n"
            "## getting started\n\nOnboarding guidance here.\n\n"
            "## work patterns\n[not yet observed]\n\n"
            "## communication style\n[not yet observed]\n"
        )
        (tmp_path / "identity").mkdir(exist_ok=True)
        (tmp_path / "identity" / "partner.md").write_text(partner_md)

        result = update_identity_section(
            "partner.md",
            "work patterns",
            "Prefers mornings",
            actor="test update identity section",
            reason="test",
        )
        assert result is True

        content = (tmp_path / "identity" / "partner.md").read_text()
        assert "Prefers mornings" in content
        assert "## communication style" in content
        assert "## getting started" not in content
        assert "Onboarding guidance" not in content
