"""Tests for LocalStore — file-based storage backend."""

from __future__ import annotations

import json
import os

from bandito.models import Span, Trace
from bandito.storage import LocalStore


class TestWriteTrace:
    def test_creates_project_directory_and_writes(self, tmp_path):
        store = LocalStore(str(tmp_path / "output"))
        trace = Trace(project="my-bot", trace_id="tr-1")
        trace.spans.append(Span(name="s", kind="llm"))
        store.write_trace(trace)

        path = tmp_path / "output" / "traces" / "my-bot" / "tr-1.json"
        assert path.exists()
        data = json.loads(path.read_text())
        assert data["trace_id"] == "tr-1"
        assert data["project"] == "my-bot"
        assert len(data["spans"]) == 1

    def test_overwrites_existing(self, tmp_path):
        store = LocalStore(str(tmp_path))
        trace = Trace(project="p", trace_id="tr-1")
        trace.spans.append(Span(name="v1", kind="llm"))
        store.write_trace(trace)

        trace.spans[0] = Span(name="v2", kind="llm")
        store.write_trace(trace)

        data = json.loads((tmp_path / "traces" / "p" / "tr-1.json").read_text())
        assert data["spans"][0]["name"] == "v2"

    def test_no_temp_files_left(self, tmp_path):
        store = LocalStore(str(tmp_path))
        trace = Trace(project="p")
        trace.spans.append(Span(name="s", kind="llm"))
        store.write_trace(trace)

        project_dir = tmp_path / "traces" / "p"
        files = os.listdir(project_dir)
        assert all(f.endswith(".json") for f in files)


class TestGetTrace:
    def test_reads_trace(self, tmp_path):
        store = LocalStore(str(tmp_path))
        trace = Trace(project="p", trace_id="tr-1", input="hello")
        store.write_trace(trace)

        result = store.get_trace("tr-1")
        assert result is not None
        assert result["trace_id"] == "tr-1"
        assert result["input"] == "hello"

    def test_returns_none_for_missing(self, tmp_path):
        store = LocalStore(str(tmp_path))
        assert store.get_trace("nope") is None

    def test_rejects_path_traversal(self, tmp_path):
        store = LocalStore(str(tmp_path))
        assert store.get_trace("../etc/passwd") is None


class TestIterTraces:
    def test_yields_all_traces(self, tmp_path):
        store = LocalStore(str(tmp_path))
        for i in range(3):
            store.write_trace(Trace(project="p", trace_id=f"tr-{i}"))

        traces = list(store.iter_traces())
        assert len(traces) == 3

    def test_filters_by_project(self, tmp_path):
        store = LocalStore(str(tmp_path))
        store.write_trace(Trace(project="bot-a", trace_id="tr-1"))
        store.write_trace(Trace(project="bot-a", trace_id="tr-2"))
        store.write_trace(Trace(project="bot-b", trace_id="tr-3"))

        a_traces = list(store.iter_traces(project="bot-a"))
        assert len(a_traces) == 2

        b_traces = list(store.iter_traces(project="bot-b"))
        assert len(b_traces) == 1

    def test_empty_directory(self, tmp_path):
        store = LocalStore(str(tmp_path))
        assert list(store.iter_traces()) == []

    def test_skips_corrupt_files(self, tmp_path):
        store = LocalStore(str(tmp_path))
        store.write_trace(Trace(project="p", trace_id="good"))

        # Write a corrupt file in the project directory
        (tmp_path / "traces" / "p" / "bad.json").write_text("not json{{{")

        traces = list(store.iter_traces())
        assert len(traces) == 1
        assert traces[0]["trace_id"] == "good"


class TestListProjects:
    def test_lists_project_directories(self, tmp_path):
        store = LocalStore(str(tmp_path))
        store.write_trace(Trace(project="alpha", trace_id="tr-1"))
        store.write_trace(Trace(project="beta", trace_id="tr-2"))
        store.write_trace(Trace(project="alpha", trace_id="tr-3"))

        projects = store.list_projects()
        assert projects == ["alpha", "beta"]

    def test_empty(self, tmp_path):
        store = LocalStore(str(tmp_path))
        assert store.list_projects() == []


class TestWriteDispatch:
    def test_dispatches_trace(self, tmp_path):
        store = LocalStore(str(tmp_path))
        trace = Trace(project="p", trace_id="tr-1")
        store.write(trace)

        assert store.get_trace("tr-1") is not None

    def test_rejects_unknown_type(self, tmp_path):
        store = LocalStore(str(tmp_path))
        try:
            store.write("not a Trace")
            assert False, "Should have raised"
        except TypeError:
            pass
