"""Tests for cleave probe module - Socratic pre-decomposition."""

from __future__ import annotations

from pathlib import Path

import pytest

from cleave.core.probe import (
    ProbeQuestion,
    ProbeResult,
    detect_stack,
    evaluate_trigger,
    find_relevant_files,
    format_probe_result,
    generate_probe_questions,
    probe_codebase,
)


class TestDetectStack:
    """Tests for technology stack detection."""

    def test_detects_python(self, tmp_path: Path) -> None:
        """Should detect Python from .py files."""
        (tmp_path / "main.py").write_text("print('hello')")

        stack = detect_stack(tmp_path)
        assert stack["language"] == "python"

    def test_detects_typescript(self, tmp_path: Path) -> None:
        """Should detect TypeScript from .ts files."""
        (tmp_path / "index.ts").write_text("const x: number = 1;")

        stack = detect_stack(tmp_path)
        assert stack["language"] == "typescript"

    def test_detects_fastapi(self, tmp_path: Path) -> None:
        """Should detect FastAPI framework."""
        (tmp_path / "main.py").write_text("from fastapi import FastAPI")

        stack = detect_stack(tmp_path)
        assert stack["language"] == "python"
        # Note: framework detection uses file patterns, not imports
        # So we need the right directory structure

    def test_detects_docker(self, tmp_path: Path) -> None:
        """Should detect Docker infrastructure."""
        (tmp_path / "Dockerfile").write_text("FROM python:3.11")

        stack = detect_stack(tmp_path)
        assert stack["infrastructure"] == "docker"

    def test_returns_none_for_unknown(self, tmp_path: Path) -> None:
        """Should return None for undetected categories."""
        # Empty directory
        stack = detect_stack(tmp_path)
        assert stack["language"] is None


class TestFindRelevantFiles:
    """Tests for keyword-based file discovery."""

    def test_finds_files_matching_keywords(self, tmp_path: Path) -> None:
        """Should find files containing directive keywords."""
        (tmp_path / "auth.py").write_text("")
        (tmp_path / "database.py").write_text("")
        (tmp_path / "unrelated.py").write_text("")

        files = find_relevant_files(tmp_path, "Add authentication")
        assert any("auth" in f for f in files)

    def test_limits_to_max_files(self, tmp_path: Path) -> None:
        """Should limit results to max_files."""
        for i in range(20):
            (tmp_path / f"auth_{i}.py").write_text("")

        files = find_relevant_files(tmp_path, "authentication", max_files=5)
        assert len(files) <= 5

    def test_skips_hidden_directories(self, tmp_path: Path) -> None:
        """Should skip hidden directories like .git."""
        hidden = tmp_path / ".git"
        hidden.mkdir()
        (hidden / "auth.py").write_text("")
        (tmp_path / "visible_auth.py").write_text("")

        files = find_relevant_files(tmp_path, "authentication")
        assert not any(".git" in f for f in files)

    def test_skips_node_modules(self, tmp_path: Path) -> None:
        """Should skip node_modules."""
        node = tmp_path / "node_modules"
        node.mkdir()
        (node / "auth.js").write_text("")
        (tmp_path / "src" / "auth.js").parent.mkdir(parents=True)
        (tmp_path / "src" / "auth.js").write_text("")

        files = find_relevant_files(tmp_path, "authentication")
        assert not any("node_modules" in f for f in files)


class TestEvaluateTrigger:
    """Tests for probe question trigger evaluation."""

    def test_always_trigger(self, tmp_path: Path) -> None:
        """Always trigger should always fire."""
        trigger = {"type": "always"}
        triggered, detail = evaluate_trigger(trigger, tmp_path, "any directive")

        assert triggered is True
        assert detail == "always"

    def test_file_exists_trigger_match(self, tmp_path: Path) -> None:
        """file_exists trigger should fire when pattern matches."""
        (tmp_path / "models").mkdir()
        (tmp_path / "models" / "user.py").write_text("")

        trigger = {"type": "file_exists", "pattern": "**/models/user*"}
        triggered, detail = evaluate_trigger(trigger, tmp_path, "")

        assert triggered is True
        assert "found" in detail

    def test_file_exists_trigger_no_match(self, tmp_path: Path) -> None:
        """file_exists trigger should not fire when pattern doesn't match."""
        trigger = {"type": "file_exists", "pattern": "**/models/user*"}
        triggered, detail = evaluate_trigger(trigger, tmp_path, "")

        assert triggered is False

    def test_file_missing_trigger_match(self, tmp_path: Path) -> None:
        """file_missing trigger should fire when pattern doesn't match."""
        trigger = {"type": "file_missing", "pattern": "**/auth/**"}
        triggered, detail = evaluate_trigger(trigger, tmp_path, "")

        assert triggered is True
        assert "missing" in detail

    def test_file_missing_trigger_no_match(self, tmp_path: Path) -> None:
        """file_missing trigger should not fire when pattern matches."""
        (tmp_path / "auth").mkdir()
        (tmp_path / "auth" / "handler.py").write_text("")

        trigger = {"type": "file_missing", "pattern": "**/auth/**"}
        triggered, detail = evaluate_trigger(trigger, tmp_path, "")

        assert triggered is False

    def test_keyword_detected_trigger_match(self, tmp_path: Path) -> None:
        """keyword_detected trigger should fire on matching keyword."""
        trigger = {"type": "keyword_detected", "keywords": ["fastapi", "flask"]}
        triggered, detail = evaluate_trigger(trigger, tmp_path, "Build a FastAPI application")

        assert triggered is True
        assert "keyword" in detail

    def test_keyword_detected_trigger_no_match(self, tmp_path: Path) -> None:
        """keyword_detected trigger should not fire without matching keyword."""
        trigger = {"type": "keyword_detected", "keywords": ["fastapi", "flask"]}
        triggered, detail = evaluate_trigger(trigger, tmp_path, "Build a Django application")

        assert triggered is False


class TestGenerateProbeQuestions:
    """Tests for probe question generation."""

    def test_generates_questions_for_pattern(self, tmp_path: Path) -> None:
        """Should generate questions when pattern matches."""
        questions = generate_probe_questions(
            directive="Add JWT authentication",
            root_dir=tmp_path,
            pattern_id="authentication",
        )

        # Authentication pattern should have at least the "always" questions
        assert len(questions) >= 1
        assert all(isinstance(q, ProbeQuestion) for q in questions)

    def test_skips_non_triggered_questions(self, tmp_path: Path) -> None:
        """Should skip questions whose triggers don't fire."""
        # Create auth directory so file_missing trigger doesn't fire
        (tmp_path / "auth").mkdir()
        (tmp_path / "auth" / "handler.py").write_text("")

        questions = generate_probe_questions(
            directive="Add authentication",
            root_dir=tmp_path,
            pattern_id="authentication",
        )

        # Should not have the "No existing auth module detected" question
        assert not any("No existing auth module" in q.question for q in questions)

    def test_includes_source_pattern(self, tmp_path: Path) -> None:
        """Questions should include their source pattern."""
        questions = generate_probe_questions(
            directive="Add JWT authentication",
            root_dir=tmp_path,
            pattern_id="authentication",
        )

        for q in questions:
            assert q.source_pattern == "authentication"


class TestProbeCodbase:
    """Tests for the main probe_codebase function."""

    def test_returns_probe_result(self, tmp_path: Path) -> None:
        """Should return a ProbeResult instance."""
        result = probe_codebase("Add user login", tmp_path)

        assert isinstance(result, ProbeResult)
        assert isinstance(result.detected_stack, dict)
        assert isinstance(result.relevant_files, list)
        assert isinstance(result.triggered_questions, list)

    def test_includes_pattern_match(self, tmp_path: Path) -> None:
        """Should include pattern match when confident."""
        result = probe_codebase(
            "Add JWT authentication with bcrypt password hashing",
            tmp_path,
        )

        # Should match authentication pattern with high confidence
        assert result.pattern_match is not None
        assert result.pattern_confidence >= 0.8

    def test_extracts_directive_keywords(self, tmp_path: Path) -> None:
        """Should extract keywords from directive."""
        result = probe_codebase("Add authentication with OAuth", tmp_path)

        assert "auth" in result.directive_keywords or "oauth" in result.directive_keywords

    def test_handles_nonexistent_directory(self) -> None:
        """Should handle nonexistent directory gracefully."""
        result = probe_codebase("test", Path("/nonexistent/path"))

        # Should return empty result, not crash
        assert result.detected_stack == {}
        assert result.relevant_files == []


class TestFormatProbeResult:
    """Tests for ProbeResult formatting."""

    def test_formats_for_json(self) -> None:
        """Should format result for JSON serialization."""
        result = ProbeResult(
            detected_stack={"language": "python"},
            relevant_files=["src/auth.py"],
            triggered_questions=[
                ProbeQuestion(
                    question="Test question?",
                    options=["a", "b"],
                    trigger_type="always",
                    trigger_detail="always",
                    source_pattern="test",
                )
            ],
            pattern_match="Authentication System",
            pattern_confidence=0.85,
            directive_keywords=["auth"],
        )

        output = format_probe_result(result)

        assert output["detected_stack"]["language"] == "python"
        assert output["relevant_files"] == ["src/auth.py"]
        assert len(output["inferred_questions"]) == 1
        assert output["pattern_match"] == "Authentication System"
        assert output["pattern_confidence"] == 0.85


class TestMultiPatternQuestionMerging:
    """Tests for merging questions from multiple patterns."""

    def test_directive_matching_multiple_patterns(self, tmp_path: Path) -> None:
        """Directive touching multiple domains should get relevant questions."""
        # This directive could match both authentication AND external_integration
        directive = "Add OAuth login with Stripe webhook integration"

        result = probe_codebase(directive, tmp_path)

        # Should have questions from at least one pattern
        assert len(result.triggered_questions) >= 1

    def test_no_duplicate_always_questions(self, tmp_path: Path) -> None:
        """Should not duplicate 'always' questions across patterns."""
        questions = generate_probe_questions(
            directive="Complex multi-domain task",
            root_dir=tmp_path,
            pattern_id=None,  # Check all patterns
        )

        question_texts = [q.question for q in questions]
        # Should be unique
        assert len(question_texts) == len(set(question_texts))
