"""Tests for error pattern learning module."""

import os
import tempfile
from datetime import datetime, timedelta
from pathlib import Path

import pytest

from oclawma.learning import (
    ErrorPatternLearner,
    ErrorPatternStore,
    ErrorRecord,
)
from oclawma.learning.integration import LearningToolWrapper, wrap_tool_with_learning
from oclawma.tools import ReadTool, ToolResult


class TestErrorRecord:
    """Test ErrorRecord dataclass."""

    def test_to_dict(self) -> None:
        """Test conversion to dictionary."""
        record = ErrorRecord(
            tool_name="read",
            error_message="File not found",
            error_type="file_not_found",
            timestamp=datetime(2024, 1, 1, 12, 0, 0),
            context={"path": "/test/file.txt"},
        )

        d = record.to_dict()

        assert d["tool_name"] == "read"
        assert d["error_message"] == "File not found"
        assert d["error_type"] == "file_not_found"
        assert d["timestamp"] == "2024-01-01T12:00:00"
        assert d["context"] == {"path": "/test/file.txt"}

    def test_from_dict(self) -> None:
        """Test creation from dictionary."""
        data = {
            "tool_name": "read",
            "error_message": "File not found",
            "error_type": "file_not_found",
            "timestamp": "2024-01-01T12:00:00",
            "context": {"path": "/test/file.txt"},
            "resolved": True,
        }

        record = ErrorRecord.from_dict(data)

        assert record.tool_name == "read"
        assert record.error_message == "File not found"
        assert record.resolved is True


class TestErrorPatternStore:
    """Test ErrorPatternStore."""

    @pytest.fixture
    def temp_db(self):
        """Create a temporary database."""
        with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
            db_path = Path(f.name)
        yield db_path
        db_path.unlink(missing_ok=True)

    @pytest.fixture
    def store(self, temp_db):
        """Create a store with temporary database."""
        return ErrorPatternStore(db_path=temp_db)

    def test_init_creates_database(self, temp_db) -> None:
        """Test initialization creates database file."""
        ErrorPatternStore(db_path=temp_db)
        assert temp_db.exists()

    def test_log_error(self, store) -> None:
        """Test logging an error."""
        record = ErrorRecord(
            tool_name="read",
            error_message="File not found",
            error_type="file_not_found",
            timestamp=datetime.now(),
        )

        error_id = store.log_error(record)

        assert error_id > 0

    def test_get_recent_errors(self, store) -> None:
        """Test retrieving recent errors."""
        # Log some errors
        for i in range(3):
            record = ErrorRecord(
                tool_name="read",
                error_message=f"Error {i}",
                error_type="file_not_found",
                timestamp=datetime.now(),
            )
            store.log_error(record)

        # Get recent errors
        errors = store.get_recent_errors(hours=24)

        assert len(errors) == 3
        assert errors[0].error_message == "Error 2"  # Most recent first

    def test_get_recent_errors_time_filter(self, store) -> None:
        """Test time filtering of recent errors."""
        # Log an old error
        old_record = ErrorRecord(
            tool_name="read",
            error_message="Old error",
            error_type="file_not_found",
            timestamp=datetime.now() - timedelta(hours=25),
        )
        store.log_error(old_record)

        # Log a recent error
        new_record = ErrorRecord(
            tool_name="read",
            error_message="New error",
            error_type="file_not_found",
            timestamp=datetime.now(),
        )
        store.log_error(new_record)

        # Get errors from last hour
        errors = store.get_recent_errors(hours=1)

        assert len(errors) == 1
        assert errors[0].error_message == "New error"

    def test_get_pattern_counts(self, store) -> None:
        """Test getting pattern counts."""
        # Log errors of different types
        for _ in range(3):
            store.log_error(
                ErrorRecord(
                    tool_name="read",
                    error_message="File not found",
                    error_type="file_not_found",
                    timestamp=datetime.now(),
                )
            )

        for _ in range(2):
            store.log_error(
                ErrorRecord(
                    tool_name="exec",
                    error_message="Permission denied",
                    error_type="permission_denied",
                    timestamp=datetime.now(),
                )
            )

        counts = store.get_pattern_counts(days=7)

        assert counts["file_not_found"] == 3
        assert counts["permission_denied"] == 2

    def test_get_all_patterns(self, store) -> None:
        """Test getting all patterns."""
        patterns = store.get_all_patterns()

        # Should have built-in patterns
        assert len(patterns) > 0

        pattern_names = [p.name for p in patterns]
        assert "file_not_found" in pattern_names
        assert "permission_denied" in pattern_names

    def test_update_pattern_occurrence(self, store) -> None:
        """Test updating pattern occurrence."""
        store.update_pattern_occurrence("file_not_found")
        store.update_pattern_occurrence("file_not_found")

        patterns = store.get_all_patterns()
        file_not_found = next(p for p in patterns if p.name == "file_not_found")

        assert file_not_found.occurrences == 2

    def test_log_correction(self, store) -> None:
        """Test logging a correction."""
        store.log_correction(
            pattern_name="file_not_found",
            original="/wrong/path",
            corrected="/correct/path",
            success=True,
        )

        success_rate = store.get_correction_success_rate("file_not_found")
        assert success_rate == 1.0

    def test_get_correction_success_rate_multiple(self, store) -> None:
        """Test success rate with multiple corrections."""
        # Log 3 successful and 1 failed correction
        for _ in range(3):
            store.log_correction("file_not_found", "a", "b", success=True)
        store.log_correction("file_not_found", "a", "b", success=False)

        success_rate = store.get_correction_success_rate("file_not_found")
        assert success_rate == 0.75

    def test_get_correction_success_rate_no_data(self, store) -> None:
        """Test success rate with no data."""
        success_rate = store.get_correction_success_rate("unknown_pattern")
        assert success_rate == 0.0


class TestErrorPatternLearner:
    """Test ErrorPatternLearner."""

    @pytest.fixture
    def temp_db(self):
        """Create a temporary database."""
        with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
            db_path = Path(f.name)
        yield db_path
        db_path.unlink(missing_ok=True)

    @pytest.fixture
    def learner(self, temp_db):
        """Create a learner with temporary database."""
        store = ErrorPatternStore(db_path=temp_db)
        return ErrorPatternLearner(store=store)

    def test_classify_error_file_not_found(self, learner) -> None:
        """Test classifying file not found error."""
        error_type = learner._classify_error("ENOENT: no such file or directory")
        assert error_type == "file_not_found"

    def test_classify_error_permission_denied(self, learner) -> None:
        """Test classifying permission denied error."""
        error_type = learner._classify_error("EACCES: permission denied")
        assert error_type == "permission_denied"

    def test_classify_error_unknown(self, learner) -> None:
        """Test classifying unknown error."""
        error_type = learner._classify_error("Some random error message")
        assert error_type == "unknown"

    def test_log_tool_error_success(self, learner) -> None:
        """Test that successful results are not logged."""
        result = ToolResult(
            tool_name="read",
            success=True,
            output="content",
        )

        record = learner.log_tool_error("read", result)

        assert record is None

    def test_log_tool_error_failure(self, learner) -> None:
        """Test logging failed tool execution."""
        result = ToolResult(
            tool_name="read",
            success=False,
            output="",
            error_message="File not found: /test.txt",
        )

        record = learner.log_tool_error("read", result)

        assert record is not None
        assert record.tool_name == "read"
        assert record.error_type == "file_not_found"

    def test_detect_recurring_patterns(self, learner) -> None:
        """Test detecting recurring patterns."""
        # Log multiple errors of the same type
        for _ in range(5):
            result = ToolResult(
                tool_name="read",
                success=False,
                output="",
                error_message="File not found",
            )
            learner.log_tool_error("read", result)

        recurring = learner.detect_recurring_patterns(min_occurrences=3, days=7)

        assert len(recurring) >= 1
        assert any(p.name == "file_not_found" for p in recurring)

    def test_suggest_correction_file_not_found(self, learner) -> None:
        """Test suggestion for file not found error."""
        # First, add some successful corrections to build confidence
        for _ in range(10):
            learner.store.log_correction("file_not_found", "/wrong", "/correct", success=True)

        params = {"path": "/nonexistent/file.txt"}
        suggestion = learner.suggest_correction("read", params, "File not found")

        assert suggestion is not None
        assert suggestion["pattern"] == "file_not_found"
        assert "suggestion" in suggestion

    def test_suggest_correction_no_match(self, learner) -> None:
        """Test suggestion for unmatched error."""
        suggestion = learner.suggest_correction(
            "read",
            {"path": "/test"},
            "Some completely random error that doesn't match any pattern",
        )

        assert suggestion is None

    def test_get_learning_stats(self, learner) -> None:
        """Test getting learning statistics."""
        # Log some errors
        for _ in range(3):
            result = ToolResult(
                tool_name="read",
                success=False,
                output="",
                error_message="File not found",
            )
            learner.log_tool_error("read", result)

        stats = learner.get_learning_stats()

        assert "total_patterns" in stats
        assert "total_errors_24h" in stats
        assert "recurring_patterns" in stats
        assert stats["total_errors_24h"] == 3

    def test_generate_report(self, learner) -> None:
        """Test report generation."""
        report = learner.generate_report()

        assert "ERROR PATTERN LEARNING REPORT" in report
        assert "Generated:" in report

    def test_suggest_file_path_correction(self, learner) -> None:
        """Test file path correction suggestion."""
        # Create a temp directory with a similar file
        with tempfile.TemporaryDirectory() as tmpdir:
            test_file = Path(tmpdir) / "testfile.txt"
            test_file.write_text("content")

            # Try to correct a typo
            params = {"path": str(Path(tmpdir) / "testfile.txt")}
            corrected = learner._suggest_file_path_correction(
                "read",
                params,
                f"File not found: {Path(tmpdir) / 'typo.txt'}",
            )

            # Should suggest creating the file (parent exists)
            assert corrected is not None
            assert "_suggestion" in corrected

    def test_suggest_type_correction(self, learner) -> None:
        """Test type correction suggestion."""
        params = {"offset": "10", "limit": "5"}
        corrected = learner._suggest_type_correction("read", params, "must be an integer")

        assert corrected is not None
        assert corrected["offset"] == 10  # Converted to int
        assert corrected["limit"] == 5  # Converted to int


class TestLearningToolWrapper:
    """Test LearningToolWrapper."""

    @pytest.fixture
    def temp_db(self):
        """Create a temporary database."""
        with tempfile.NamedTemporaryFile(suffix=".db", delete=False) as f:
            db_path = Path(f.name)
        yield db_path
        db_path.unlink(missing_ok=True)

    @pytest.mark.asyncio
    async def test_successful_execution_no_logging(self, temp_db) -> None:
        """Test that successful executions don't log errors."""
        tool = ReadTool()
        learner = ErrorPatternLearner(store=ErrorPatternStore(db_path=temp_db))
        wrapper = LearningToolWrapper(tool, learner=learner)

        # Create a temp file
        with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
            f.write("test content")
            path = f.name

        try:
            result = await wrapper.execute(path=path)

            assert result.success is True

            # Should not have logged any errors
            errors = learner.store.get_recent_errors(hours=1)
            assert len(errors) == 0
        finally:
            os.unlink(path)

    @pytest.mark.asyncio
    async def test_failed_execution_logs_error(self, temp_db) -> None:
        """Test that failed executions are logged."""
        tool = ReadTool()
        learner = ErrorPatternLearner(store=ErrorPatternStore(db_path=temp_db))
        wrapper = LearningToolWrapper(tool, learner=learner)

        result = await wrapper.execute(path="/nonexistent/file.txt")

        assert result.success is False

        # Should have logged the error
        errors = learner.store.get_recent_errors(hours=1)
        assert len(errors) == 1
        assert errors[0].tool_name == "read"

    @pytest.mark.asyncio
    async def test_failed_execution_with_auto_correct_disabled(self, temp_db) -> None:
        """Test failed execution with auto-correction disabled."""
        tool = ReadTool()
        learner = ErrorPatternLearner(store=ErrorPatternStore(db_path=temp_db))
        wrapper = LearningToolWrapper(tool, learner=learner, enable_auto_correct=False)

        result = await wrapper.execute(path="/nonexistent/file.txt")

        assert result.success is False
        assert "auto_corrected" not in result.metadata

    def test_name_property(self) -> None:
        """Test name property delegates to wrapped tool."""
        tool = ReadTool()
        wrapper = LearningToolWrapper(tool)

        assert wrapper.name == "read"

    def test_description_property(self) -> None:
        """Test description property delegates to wrapped tool."""
        tool = ReadTool()
        wrapper = LearningToolWrapper(tool)

        assert wrapper.description == tool.description


class TestWrapToolWithLearning:
    """Test wrap_tool_with_learning function."""

    def test_wraps_tool(self) -> None:
        """Test that tool is wrapped correctly."""
        tool = ReadTool()
        wrapped = wrap_tool_with_learning(tool)

        assert isinstance(wrapped, LearningToolWrapper)
        assert wrapped.name == "read"
