"""Tests for determinism checkers (DET001-DET009)."""

import pytest
from pathlib import Path

from stablestack.checkers.determinism import (
    DictIterationChecker,
    SetIterationChecker,
    ListSetDedupChecker,
    RandomWithoutSeedChecker,
    UncachedLlmCallChecker,
)


class TestDictIterationChecker:
    """Tests for DET001: unsorted-dict-iteration."""

    @pytest.fixture
    def checker(self):
        return DictIterationChecker()

    def test_detects_unsorted_items(self, checker, temp_py_file):
        """Should detect iteration over dict.items() without sorting."""
        code = '''
for key, value in config.items():
    print(key, value)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "DET001"

    def test_detects_unsorted_keys(self, checker, temp_py_file):
        """Should detect iteration over dict.keys() without sorting."""
        code = '''
for key in data.keys():
    process(key)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_detects_unsorted_values(self, checker, temp_py_file):
        """Should detect iteration over dict.values() without sorting."""
        code = '''
for value in mapping.values():
    handle(value)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_allows_sorted_items(self, checker, temp_py_file):
        """Should not flag sorted dict iteration."""
        code = '''
for key, value in sorted(config.items()):
    print(key, value)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_sorted_keys(self, checker, temp_py_file):
        """Should not flag sorted keys iteration."""
        code = '''
for key in sorted(data.keys()):
    process(key)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0


class TestSetIterationChecker:
    """Tests for DET002: unsorted-set-iteration."""

    @pytest.fixture
    def checker(self):
        return SetIterationChecker()

    def test_detects_unsorted_set(self, checker, temp_py_file):
        """Should detect iteration over set() without sorting."""
        code = '''
for item in set(items):
    process(item)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "DET002"

    def test_allows_sorted_set(self, checker, temp_py_file):
        """Should not flag sorted set iteration."""
        code = '''
for item in sorted(set(items)):
    process(item)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0


class TestListSetDedupChecker:
    """Tests for DET004: list-set-dedup."""

    @pytest.fixture
    def checker(self):
        return ListSetDedupChecker()

    def test_detects_list_set_pattern(self, checker, temp_py_file):
        """Should detect list(set(...)) pattern."""
        code = '''
unique = list(set(items))
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "DET004"

    def test_allows_sorted_set(self, checker, temp_py_file):
        """Should not flag sorted(set(...)) pattern."""
        code = '''
unique = sorted(set(items))
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_allows_dict_fromkeys(self, checker, temp_py_file):
        """Should not flag dict.fromkeys() pattern."""
        code = '''
unique = list(dict.fromkeys(items))
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0


class TestRandomWithoutSeedChecker:
    """Tests for DET005: random-without-seed."""

    @pytest.fixture
    def checker(self):
        return RandomWithoutSeedChecker()

    def test_detects_random_choice(self, checker, temp_py_file):
        """Should detect random.choice() usage."""
        code = '''
import random
choice = random.choice(options)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "DET005"

    def test_detects_random_shuffle(self, checker, temp_py_file):
        """Should detect random.shuffle() usage."""
        code = '''
import random
random.shuffle(items)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1

    def test_allows_seeded_random(self, checker, temp_py_file):
        """Should not flag random usage after seed is set in file."""
        code = '''
import random
random.seed(42)
choice = random.choice(options)
'''
        path = temp_py_file(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0


class TestUncachedLlmCallChecker:
    """Tests for DET009: uncached-llm-call."""

    @pytest.fixture
    def checker(self):
        return UncachedLlmCallChecker()

    @pytest.fixture
    def project_with_cache(self, tmp_path):
        """Create a project directory with stablestack_cache.py present."""
        # Create project markers
        (tmp_path / ".git").mkdir()
        (tmp_path / "stablestack_cache.py").write_text("# cache module")
        return tmp_path

    @pytest.fixture
    def project_without_cache(self, tmp_path):
        """Create a project directory without stablestack_cache.py."""
        (tmp_path / ".git").mkdir()
        return tmp_path

    def test_detects_uncached_openai_chat(self, checker, project_with_cache):
        """Should detect uncached client.chat.completions.create()."""
        code = '''
def get_summary(text):
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": text}]
    )
    return response.choices[0].message.content
'''
        path = project_with_cache / "app.py"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "DET009"

    def test_detects_uncached_anthropic(self, checker, project_with_cache):
        """Should detect uncached client.messages.create()."""
        code = '''
def ask_claude(prompt):
    response = client.messages.create(
        model="claude-sonnet-4-5-20250929",
        messages=[{"role": "user", "content": prompt}]
    )
    return response.content[0].text
'''
        path = project_with_cache / "app.py"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 1
        assert findings[0].rule_id == "DET009"

    def test_allows_cached_llm_call(self, checker, project_with_cache):
        """Should not flag LLM call when function has @cache decorator."""
        code = '''
from stablestack_cache import cache

@cache
def get_summary(text):
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": text}]
    )
    return response.choices[0].message.content
'''
        path = project_with_cache / "app.py"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_ignores_non_llm_create(self, checker, project_with_cache):
        """Should not flag non-LLM .create() calls like User.objects.create()."""
        code = '''
def create_user(name):
    user = User.objects.create(name=name)
    return user
'''
        path = project_with_cache / "app.py"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0

    def test_no_findings_without_cache_module(self, checker, project_without_cache):
        """Should return empty findings when stablestack_cache.py is not present."""
        code = '''
def get_summary(text):
    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": text}]
    )
    return response.choices[0].message.content
'''
        path = project_without_cache / "app.py"
        path.write_text(code)
        findings = checker.check_file(path, code)
        assert len(findings) == 0
