"""Tests for cortex.hippo.bootstrap reconciliation helpers."""

from __future__ import annotations

import json

import pytest

from cortex.hippo.bootstrap import (
    is_bootstrap_message,
    message_identity,
    read_leaf_path_messages,
)


# ── is_bootstrap_message ────────────────────────────────────────────────────


class TestIsBootstrapMessageValid:
    """Content-based messages."""

    def test_with_content(self):
        assert is_bootstrap_message({"role": "user", "content": "hello"}) is True

    def test_with_empty_content(self):
        assert is_bootstrap_message({"role": "assistant", "content": ""}) is True

    def test_with_extra_keys(self):
        msg = {"role": "system", "content": "hi", "meta": 42}
        assert is_bootstrap_message(msg) is True


class TestIsBootstrapMessageValidCommand:
    """Command/output-based messages."""

    def test_command_and_output(self):
        msg = {"role": "tool", "command": "ls", "output": "file.txt"}
        assert is_bootstrap_message(msg) is True

    def test_command_output_and_content(self):
        msg = {"role": "tool", "command": "ls", "output": "", "content": "x"}
        assert is_bootstrap_message(msg) is True


class TestIsBootstrapMessageInvalid:
    """Invalid candidates."""

    @pytest.mark.parametrize(
        "value",
        [
            None,
            42,
            "string",
            [],
            True,
            {"content": "no role"},
            {"role": 123, "content": "bad role type"},
            {"role": "user"},  # no content, no command+output
            {"role": "user", "command": "ls"},  # command without output
            {"role": "user", "output": "x"},  # output without command
        ],
    )
    def test_rejects(self, value):
        assert is_bootstrap_message(value) is False


# ── read_leaf_path_messages ──────────────────────────────────────────────────


def test_read_leaf_path_json_array(tmp_path):
    data = [
        {"role": "user", "content": "hi"},
        {"role": "assistant", "content": "hey"},
        {"not": "valid"},
    ]
    f = tmp_path / "session.json"
    f.write_text(json.dumps(data), encoding="utf-8")
    result = read_leaf_path_messages(f)
    assert len(result) == 2
    assert result[0]["content"] == "hi"
    assert result[1]["content"] == "hey"


def test_read_leaf_path_jsonl(tmp_path):
    lines = [
        json.dumps({"role": "user", "content": "a"}),
        json.dumps({"role": "assistant", "content": "b"}),
    ]
    f = tmp_path / "session.jsonl"
    f.write_text("\n".join(lines), encoding="utf-8")
    result = read_leaf_path_messages(f)
    assert len(result) == 2
    assert result[0]["content"] == "a"


def test_read_leaf_path_jsonl_with_message_wrapper(tmp_path):
    lines = [
        json.dumps({"message": {"role": "user", "content": "wrapped"}}),
        json.dumps({"role": "assistant", "content": "direct"}),
    ]
    f = tmp_path / "session.jsonl"
    f.write_text("\n".join(lines), encoding="utf-8")
    result = read_leaf_path_messages(f)
    assert len(result) == 2
    assert result[0]["content"] == "wrapped"
    assert result[1]["content"] == "direct"


def test_read_leaf_path_missing_file():
    result = read_leaf_path_messages("/nonexistent/path/session.json")
    assert result == []


def test_read_leaf_path_empty_file(tmp_path):
    f = tmp_path / "empty.json"
    f.write_text("", encoding="utf-8")
    assert read_leaf_path_messages(f) == []

    f2 = tmp_path / "whitespace.json"
    f2.write_text("   \n\n  ", encoding="utf-8")
    assert read_leaf_path_messages(f2) == []


def test_read_leaf_path_malformed_lines(tmp_path):
    lines = [
        json.dumps({"role": "user", "content": "ok"}),
        "not json at all {{{",
        "",
        json.dumps({"role": "assistant", "content": "also ok"}),
        '{"broken": true',  # invalid JSON
    ]
    f = tmp_path / "messy.jsonl"
    f.write_text("\n".join(lines), encoding="utf-8")
    result = read_leaf_path_messages(f)
    assert len(result) == 2
    assert result[0]["content"] == "ok"
    assert result[1]["content"] == "also ok"


# ── message_identity ────────────────────────────────────────────────────────


def test_message_identity():
    assert message_identity("user", "hello") == "user\x00hello"
    assert message_identity("assistant", "") == "assistant\x00"
    # Verify the separator is a null byte
    key = message_identity("role", "content")
    assert "\x00" in key
    parts = key.split("\x00")
    assert parts == ["role", "content"]
