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

from __future__ import annotations

import argparse
import re
import subprocess
from pathlib import Path
from typing import Any

import pytest

from solstone.think import core_handshake
from solstone.think.indexer import native


def _args(**overrides: Any) -> argparse.Namespace:
    values: dict[str, Any] = {
        "rescan": False,
        "rescan_full": False,
        "rescan_file": None,
        "rebuild_edges": False,
        "reset": False,
        "day": None,
        "day_from": None,
        "day_to": None,
        "facet": None,
        "agent": None,
        "stream": None,
        "query": None,
        "limit": 10,
        "offset": 0,
        "top": 5,
        "verbose": False,
        "debug": False,
    }
    values.update(overrides)
    return argparse.Namespace(**values)


def _ok() -> core_handshake.CoreHandshakeResult:
    return core_handshake.CoreHandshakeResult("ok")


def _raise_unexpected(name: str):
    def fail(*_args, **_kwargs):
        raise AssertionError(f"{name} should not be called")

    return fail


def _native_argv(*flags: str, journal: str = "/tmp/journal") -> list[str]:
    return ["/tmp/bin/solstone-core", "indexer", "--journal", journal, *flags]


def _route(
    args: argparse.Namespace,
    *,
    native_returncode: int = 0,
    native_runner=None,
    handshake_checker=None,
    helper_path: Path | None = None,
    journal: str = "/tmp/journal",
    platform_tuple: tuple[str, str] = ("linux", "x86_64"),
    platform_tags: set[str] | None = None,
) -> tuple[int, list[list[str]]]:
    native_argvs: list[list[str]] = []

    def default_native_runner(argv: list[str], *, check: bool = False):
        assert check is False
        native_argvs.append(argv)
        return subprocess.CompletedProcess(argv, native_returncode)

    result = native.run_native_indexer(
        args,
        journal,
        handshake_checker=handshake_checker or _ok,
        helper_locator=lambda: helper_path or Path("/tmp/bin/solstone-core"),
        native_runner=native_runner or default_native_runner,
        platform_reader=lambda: platform_tuple,
        platform_tag_reader=lambda: platform_tags or {"manylinux2014_x86_64"},
    )
    return result, native_argvs


@pytest.mark.parametrize(
    ("platform_tuple", "platform_tags", "expected"),
    [
        (("linux", "x86_64"), {"manylinux2014_x86_64"}, True),
        (("linux", "x86_64"), {"musllinux_1_2_x86_64"}, False),
        (("linux", "aarch64"), {"musllinux_1_2_aarch64"}, False),
        (("darwin", "x86_64"), {"macosx_14_0_x86_64"}, False),
        (("linux", "ppc64le"), {"manylinux_2_17_ppc64le"}, False),
    ],
)
def test_runtime_coverage_uses_platform_tuple_and_packaging_tags(
    platform_tuple: tuple[str, str],
    platform_tags: set[str],
    expected: bool,
) -> None:
    assert (
        native.runtime_has_solstone_core_wheel_coverage(
            platform_reader=lambda: platform_tuple,
            platform_tag_reader=lambda: platform_tags,
        )
        is expected
    )


def test_runtime_coverage_splits_compressed_linux_tag_set() -> None:
    assert native.runtime_has_solstone_core_wheel_coverage(
        platform_reader=lambda: ("linux", "x86_64"),
        platform_tag_reader=lambda: {"manylinux_2_17_x86_64"},
    )


def test_uncovered_runtime_returns_78_without_handshake(
    capsys: pytest.CaptureFixture[str],
) -> None:
    result, native_argvs = _route(
        _args(rescan=True),
        platform_tags={"musllinux_1_2_x86_64"},
        handshake_checker=_raise_unexpected("handshake_checker"),
    )

    assert result == core_handshake.EX_CONFIG
    assert native_argvs == []
    assert "This host has no compatible solstone-core wheel" in capsys.readouterr().err


def test_handshake_skip_reports_source_checkout_guidance(
    capsys: pytest.CaptureFixture[str],
) -> None:
    result, native_argvs = _route(
        _args(rescan=True),
        handshake_checker=lambda: core_handshake.CoreHandshakeResult("skip", "reason"),
    )

    assert result == core_handshake.EX_CONFIG
    assert native_argvs == []
    assert capsys.readouterr().err.strip() == (
        "journal indexer requires solstone-core for command writes, but "
        "solstone-core distribution metadata is missing in this source checkout. "
        "Run make install to restore the journal-host environment, then rerun "
        "journal indexer."
    )


def test_handshake_fail_passes_through_context(
    capsys: pytest.CaptureFixture[str],
) -> None:
    result, native_argvs = _route(
        _args(rescan=True),
        handshake_checker=lambda: core_handshake.CoreHandshakeResult("fail", "reason"),
    )

    assert result == core_handshake.EX_CONFIG
    assert native_argvs == []
    assert capsys.readouterr().err.strip() == (
        "journal indexer cannot run native command writes: reason"
    )


@pytest.mark.parametrize(
    ("overrides", "expected_flags"),
    [
        ({"reset": True}, ["--reset"]),
        ({"rebuild_edges": True}, ["--rebuild-edges"]),
        ({"rescan": True}, ["--rescan"]),
        ({"rescan_full": True}, ["--rescan-full"]),
        (
            {"rescan_file": "chronicle/today.md"},
            ["--rescan-file", str(Path("/tmp/journal/chronicle/today.md").resolve())],
        ),
    ],
)
def test_write_operations_invoke_native_with_expected_flags(
    overrides: dict[str, Any],
    expected_flags: list[str],
) -> None:
    result, native_argvs = _route(_args(**overrides))

    assert result == 0
    assert native_argvs == [_native_argv(*expected_flags)]


@pytest.mark.parametrize(
    ("overrides", "expected_flags"),
    [
        (
            {"reset": True, "rebuild_edges": True, "rescan_full": True},
            ["--reset", "--rebuild-edges", "--rescan-full"],
        ),
        (
            {"rebuild_edges": True, "rescan_full": True},
            ["--rebuild-edges", "--rescan-full"],
        ),
        ({"reset": True, "rescan_full": True}, ["--reset", "--rescan-full"]),
        ({"rescan": True, "rescan_full": True}, ["--rescan-full"]),
    ],
)
def test_composed_write_order(
    overrides: dict[str, Any],
    expected_flags: list[str],
) -> None:
    result, native_argvs = _route(_args(**overrides))

    assert result == 0
    assert native_argvs == [_native_argv(*expected_flags)]


def test_native_tail_drops_verbose_debug_and_query_filters() -> None:
    result, native_argvs = _route(
        _args(
            rescan=True,
            verbose=True,
            debug=True,
            day="20260101",
            facet="work",
            agent="flow",
            stream="archon",
            limit=99,
            offset=4,
            top=7,
            query="needle",
        )
    )

    assert result == 0
    assert native_argvs == [_native_argv("--rescan")]


def test_rescan_file_normalizes_chronicle_prefixed_relative_to_absolute(
    tmp_path: Path,
) -> None:
    journal = tmp_path / "journal"
    rel = "chronicle/20240101/talents/flow.md"
    (journal / "chronicle" / "20240101" / "talents").mkdir(parents=True)

    result, native_argvs = _route(
        _args(rescan_file=rel),
        journal=str(journal),
    )

    assert result == 0
    assert native_argvs == [
        [
            "/tmp/bin/solstone-core",
            "indexer",
            "--journal",
            str(journal),
            "--rescan-file",
            str((journal / rel).resolve()),
        ]
    ]


def test_empty_tail_raises_runtime_error(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setattr(native, "_build_operation_flags", lambda _args, _journal: [])

    with pytest.raises(RuntimeError, match=re.escape(native.EMPTY_TAIL_MESSAGE)):
        _route(
            _args(rescan=True),
            native_runner=_raise_unexpected("native_runner"),
        )


@pytest.mark.parametrize(
    ("returncode", "expected_result", "expected_error"),
    [
        (
            64,
            64,
            "journal indexer usage error: native solstone-core indexer rejected "
            "the command arguments with exit 64. This is a command "
            "argument-construction bug; update solstone or report the full "
            "journal indexer command.",
        ),
        (
            69,
            69,
            "journal indexer native command exited 69 (unsupported input). The "
            "Python command-write path has been retired; remove or change the "
            "unsupported input and rerun journal indexer.",
        ),
        (
            75,
            75,
            "journal indexer native command exited 75 (temporary failure). Fix "
            "the reported cause and rerun journal indexer.",
        ),
        (
            -9,
            75,
            "journal indexer native command died from signal 9 (returncode -9); "
            "treating as temporary failure. Fix the cause and rerun journal "
            "indexer.",
        ),
        (
            12,
            12,
            "journal indexer native command exited 12. Fix the reported cause "
            "and rerun journal indexer.",
        ),
    ],
)
def test_native_exit_mappings(
    returncode: int,
    expected_result: int,
    expected_error: str,
    capsys: pytest.CaptureFixture[str],
) -> None:
    result, native_argvs = _route(
        _args(rescan=True),
        native_returncode=returncode,
    )

    assert result == expected_result
    assert native_argvs == [_native_argv("--rescan")]
    assert capsys.readouterr().err.strip() == expected_error


def test_native_launch_oserror_maps_to_tempfail_without_composed_warning(
    capsys: pytest.CaptureFixture[str],
) -> None:
    def native_runner(_argv: list[str], *, check: bool = False):
        assert check is False
        raise OSError("missing helper")

    result, native_argvs = _route(
        _args(reset=True, rebuild_edges=True, rescan=True),
        native_runner=native_runner,
    )

    assert result == 75
    assert native_argvs == []
    err = capsys.readouterr().err.strip()
    assert err == (
        "journal indexer failed to launch solstone-core indexer: missing helper. "
        "Run make install in a source checkout or reinstall solstone-journal, "
        "then rerun journal indexer."
    )
    assert native.COMPOSED_COMMAND_WARNING not in err


@pytest.mark.parametrize("returncode", [69, 75, -9, 12])
def test_composed_nonzero_after_process_start_warns(
    returncode: int,
    capsys: pytest.CaptureFixture[str],
) -> None:
    result, native_argvs = _route(
        _args(reset=True, rebuild_edges=True, rescan=True),
        native_returncode=returncode,
    )

    assert result == (75 if returncode < 0 else returncode)
    assert native_argvs == [_native_argv("--reset", "--rebuild-edges", "--rescan")]
    assert native.COMPOSED_COMMAND_WARNING in capsys.readouterr().err


def test_composed_native_usage_64_does_not_warn(
    capsys: pytest.CaptureFixture[str],
) -> None:
    result, native_argvs = _route(
        _args(reset=True, rebuild_edges=True, rescan=True),
        native_returncode=64,
    )

    assert result == 64
    assert native_argvs == [_native_argv("--reset", "--rebuild-edges", "--rescan")]
    assert native.COMPOSED_COMMAND_WARNING not in capsys.readouterr().err
