"""Tests for promptkit.store module."""

import json
from pathlib import Path
from typing import Optional

import pytest

from promptkit.config import Config
from promptkit.prompt import Prompt, PromptVersion
from promptkit.store import PromptStore


class TestStoreInit:
    """Tests for PromptStore construction."""

    def test_requires_initialized_project(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
        monkeypatch.chdir(tmp_path)
        with pytest.raises(RuntimeError, match="No promptkit project found"):
            PromptStore()

    def test_accepts_config(self, config: Config) -> None:
        store = PromptStore(config)
        assert store is not None


class TestStoreCommit:
    """Tests for PromptStore.commit — the git-like save operation."""

    def test_commit_new_prompt(self, store: PromptStore) -> None:
        v = store.commit("my-prompt", "Hello world", message="first")
        assert v.version == 1
        assert v.content == "Hello world"

    def test_commit_increments_version(self, store: PromptStore) -> None:
        store.commit("my-prompt", "v1 content")
        v2 = store.commit("my-prompt", "v2 content", message="update")
        assert v2.version == 2

    def test_commit_duplicate_content_raises(self, store: PromptStore) -> None:
        store.commit("my-prompt", "Same content")
        with pytest.raises(ValueError, match="Content unchanged"):
            store.commit("my-prompt", "Same content")

    def test_commit_sets_description(self, store: PromptStore) -> None:
        store.commit("my-prompt", "content", description="A greeting prompt")
        prompt = store.load("my-prompt")
        assert prompt is not None
        assert prompt.description == "A greeting prompt"

    def test_commit_persists_to_disk(self, store: PromptStore) -> None:
        store.commit("persist-test", "persisted content")
        # Clear cache and reload
        store._cache.clear()
        prompt = store.load("persist-test")
        assert prompt is not None
        assert prompt.latest is not None
        assert prompt.latest.content == "persisted content"


class TestStoreLoad:
    """Tests for PromptStore.load."""

    def test_load_existing(self, store: PromptStore) -> None:
        store.commit("loadable", "some content")
        prompt = store.load("loadable")
        assert prompt is not None
        assert prompt.name == "loadable"

    def test_load_nonexistent_returns_none(self, store: PromptStore) -> None:
        assert store.load("missing") is None

    def test_load_uses_cache(self, store: PromptStore) -> None:
        store.commit("cached", "content")
        p1 = store.load("cached")
        p2 = store.load("cached")
        assert p1 is p2  # same object from cache


class TestStoreSave:
    """Tests for PromptStore.save."""

    def test_save_creates_prompt_dir(self, store: PromptStore) -> None:
        p = Prompt(name="dir-test")
        p.add_version("content")
        store.save(p)
        prompt_dir = store._prompts_dir / "dir-test"
        assert prompt_dir.is_dir()

    def test_save_creates_metadata_file(self, store: PromptStore) -> None:
        p = Prompt(name="meta-test")
        p.add_version("content")
        store.save(p)
        meta = store._prompts_dir / "meta-test" / "prompt.json"
        assert meta.exists()
        data = json.loads(meta.read_text())
        assert data["name"] == "meta-test"

    def test_save_creates_version_md_files(self, store: PromptStore) -> None:
        p = Prompt(name="md-test")
        p.add_version("First version")
        p.add_version("Second version")
        store.save(p)
        assert (store._prompts_dir / "md-test" / "v1.md").exists()
        assert (store._prompts_dir / "md-test" / "v2.md").exists()


class TestStoreDelete:
    """Tests for PromptStore.delete."""

    def test_delete_existing(self, store: PromptStore) -> None:
        store.commit("to-delete", "content")
        assert store.delete("to-delete") is True
        assert store.load("to-delete") is None

    def test_delete_nonexistent_returns_false(self, store: PromptStore) -> None:
        assert store.delete("never-existed") is False

    def test_delete_removes_from_cache(self, store: PromptStore) -> None:
        store.commit("cache-del", "content")
        store.load("cache-del")  # populate cache
        store.delete("cache-del")
        assert "cache-del" not in store._cache


class TestStoreListPrompts:
    """Tests for PromptStore.list_prompts."""

    def test_list_empty(self, store: PromptStore) -> None:
        assert store.list_prompts() == []

    def test_list_multiple(self, store: PromptStore) -> None:
        store.commit("alpha", "a")
        store.commit("beta", "b")
        names = sorted([p.name for p in store.list_prompts()])
        assert names == ["alpha", "beta"]


class TestStoreSearch:
    """Tests for PromptStore.search."""

    def test_search_by_name(self, store: PromptStore) -> None:
        store.commit("greeting-prompt", "Hello user")
        store.commit("farewell-prompt", "Goodbye user")
        results = store.search("greeting")
        assert len(results) == 1
        assert results[0].name == "greeting-prompt"

    def test_search_by_content(self, store: PromptStore) -> None:
        store.commit("alpha", "The quick brown fox")
        store.commit("beta", "The lazy dog")
        results = store.search("brown fox")
        assert len(results) == 1
        assert results[0].name == "alpha"

    def test_search_no_results(self, store: PromptStore) -> None:
        store.commit("alpha", "content")
        assert store.search("zzzzz") == []

    def test_search_case_insensitive(self, store: PromptStore) -> None:
        store.commit("upper", "HELLO WORLD")
        results = store.search("hello")
        assert len(results) == 1


class TestStoreTag:
    """Tests for PromptStore.tag / untag."""

    def test_tag_prompt(self, store: PromptStore) -> None:
        store.commit("tagged", "content")
        store.tag("tagged", "prod")
        prompt = store.load("tagged")
        assert prompt is not None
        assert "prod" in prompt.tags

    def test_tag_specific_version(self, store: PromptStore) -> None:
        store.commit("multi", "v1")
        store.commit("multi", "v2")
        store.tag("multi", "stable", version=1)
        prompt = store.load("multi")
        assert prompt is not None
        assert prompt.tags["stable"] == 1

    def test_tag_nonexistent_raises(self, store: PromptStore) -> None:
        with pytest.raises(ValueError, match="not found"):
            store.tag("missing", "prod")

    def test_untag(self, store: PromptStore) -> None:
        store.commit("untagged", "content")
        store.tag("untagged", "staging")
        assert store.untag("untagged", "staging") is True

    def test_untag_nonexistent_prompt_raises(self, store: PromptStore) -> None:
        with pytest.raises(ValueError, match="not found"):
            store.untag("missing", "staging")


class TestStoreGet:
    """Tests for PromptStore.get."""

    def test_get_content(self, store: PromptStore) -> None:
        store.commit("readable", "Hello world")
        content = store.get("readable")
        assert content == "Hello world"

    def test_get_with_variables(self, store: PromptStore) -> None:
        store.commit("template", "Hello {{name}}")
        content = store.get("template", variables={"name": "Alice"})
        assert content == "Hello Alice"

    def test_get_specific_version(self, store: PromptStore) -> None:
        store.commit("versioned", "first")
        store.commit("versioned", "second")
        assert store.get("versioned", version=1) == "first"
        assert store.get("versioned", version=2) == "second"

    def test_get_nonexistent_returns_none(self, store: PromptStore) -> None:
        assert store.get("missing") is None


class TestStoreDiff:
    """Tests for PromptStore.diff."""

    def test_diff_two_versions(self, store: PromptStore) -> None:
        store.commit("diff-test", "Version one content")
        store.commit("diff-test", "Version two content")
        result = store.diff("diff-test", 1, 2)
        assert isinstance(result, list)

    def test_diff_nonexistent_prompt_raises(self, store: PromptStore) -> None:
        with pytest.raises(ValueError, match="not found"):
            store.diff("missing", 1, 2)


class TestStoreExportImport:
    """Tests for export_prompt / import_prompt."""

    def test_export_prompt(self, store: PromptStore, tmp_path: Path) -> None:
        store.commit("exportable", "Export this content")
        output = tmp_path / "exported.md"
        store.export_prompt("exportable", output)
        assert output.read_text() == "Export this content"

    def test_export_nonexistent_raises(self, store: PromptStore, tmp_path: Path) -> None:
        with pytest.raises(ValueError, match="not found"):
            store.export_prompt("missing", tmp_path / "out.md")

    def test_import_prompt(self, store: PromptStore, prompt_file: Path) -> None:
        v = store.import_prompt("imported", prompt_file)
        assert v.version == 1
        loaded = store.load("imported")
        assert loaded is not None

    def test_import_nonexistent_file_raises(self, store: PromptStore, tmp_path: Path) -> None:
        with pytest.raises(FileNotFoundError):
            store.import_prompt("bad", tmp_path / "nonexistent.md")


class TestStoreStats:
    """Tests for PromptStore.stats."""

    def test_stats_empty(self, store: PromptStore) -> None:
        s = store.stats()
        assert s["total_prompts"] == 0
        assert s["total_versions"] == 0
        assert s["total_tags"] == 0

    def test_stats_with_data(self, store: PromptStore) -> None:
        store.commit("p1", "content-1")
        store.commit("p1", "content-2")
        store.commit("p2", "content-a")
        store.tag("p1", "prod")
        s = store.stats()
        assert s["total_prompts"] == 2
        assert s["total_versions"] == 3
        # p1 has "latest" + "prod" tags, p2 has "latest" tag
        assert s["total_tags"] >= 3
