"""
Tests for RAW table SHACL validation handlers.

Tests cover:
1. Incremental validation with timestamp-based processing
2. Historic validation with cursor-based partitioning
3. State management (RawValidationState, FunctionValidationState)
4. Timeout and resumption behavior
5. Integration with Records API
"""

from __future__ import annotations

from typing import Any
from unittest.mock import Mock, patch

import pytest


class TestRawValidationIncremental:
    """Test incremental RAW validation handler."""

    def test_incremental_validation_first_run(self) -> None:
        """Test first run with no previous state."""
        from cognite_data_quality._function_code.handlers.raw_validation import handle_raw_validation_incremental

        # Mock client
        mock_client = Mock()
        mock_client.data_modeling.instances.retrieve = Mock(return_value=Mock(nodes=[]))  # No previous state

        # Mock SHACL validation result
        mock_validation_result = Mock()
        mock_validation_result.conforms = True
        mock_validation_result.report_graph = Mock()

        data = {
            "db_name": "test_db",
            "table_name": "test_table",
            "shacl_rules": "@prefix sh: <http://www.w3.org/ns/shacl#> .",
            "records_config": {
                "rule_set_id": "TestRuleSet",
                "rule_set_version": "1.0",
            },
        }

        with patch("cognite_data_quality._function_code.handlers.raw_validation.NeatSession") as mock_neat:
            mock_session = Mock()
            mock_session.validate_instances.with_shacl_raw.return_value = mock_validation_result
            mock_neat.return_value = mock_session

            with patch(
                "cognite_data_quality._function_code.handlers.raw_validation.post_validation_results_to_records"
            ):
                result = handle_raw_validation_incremental(data, mock_client)

        assert result["conforms"] is True
        assert "rows_validated" in result

    def test_incremental_validation_with_existing_state(self) -> None:
        """Test incremental validation resuming from previous state."""
        from cognite_data_quality._function_code.handlers.raw_validation import handle_raw_validation_incremental

        # Mock client with existing state
        mock_client = Mock()
        mock_state_node = Mock()
        mock_state_node.properties = {
            "dataQuality": {
                "RawValidationState/1": {
                    "last_processed_timestamp": 1000000,
                    "rows_processed": 500,
                }
            }
        }
        mock_client.data_modeling.instances.retrieve = Mock(return_value=Mock(nodes=[mock_state_node]))

        # Mock validation result
        mock_validation_result = Mock()
        mock_validation_result.conforms = True
        mock_validation_result.report_graph = Mock()

        data = {
            "db_name": "test_db",
            "table_name": "test_table",
            "shacl_rules": "@prefix sh: <http://www.w3.org/ns/shacl#> .",
            "records_config": {"rule_set_id": "TestRuleSet"},
        }

        with patch("cognite_data_quality._function_code.handlers.raw_validation.NeatSession") as mock_neat:
            mock_session = Mock()
            mock_session.validate_instances.with_shacl_raw.return_value = mock_validation_result
            mock_neat.return_value = mock_session

            with patch(
                "cognite_data_quality._function_code.handlers.raw_validation.post_validation_results_to_records"
            ):
                result = handle_raw_validation_incremental(data, mock_client)

        # Verify it used min_last_updated_time from state
        assert result["conforms"] is True


class TestRawValidationHistoric:
    """Test historic RAW validation handler with cursor-based partitioning."""

    def test_historic_validation_with_cursor(self) -> None:
        """Test historic validation using cursor from getCursors API."""
        from cognite_data_quality._function_code.handlers.raw_validation import handle_raw_validation_historic

        # Mock client
        mock_client = Mock()
        mock_client.data_modeling.instances.retrieve = Mock(return_value=Mock(nodes=[]))  # No saved state

        # Mock validation result
        mock_validation_result = Mock()
        mock_validation_result.conforms = True
        mock_validation_result.report_graph = Mock()

        data = {
            "db_name": "test_db",
            "table_name": "test_table",
            "partition_id": "partition_0",
            "cursor": "test_cursor_abc123",
            "shacl_rules": "@prefix sh: <http://www.w3.org/ns/shacl#> .",
            "records_config": {"rule_set_id": "TestRuleSet"},
        }

        with patch("cognite_data_quality._function_code.handlers.raw_validation.NeatSession") as mock_neat:
            mock_session = Mock()
            # Return empty result (no more rows) to complete quickly
            mock_empty_result = Mock()
            mock_empty_result.conforms = True
            mock_empty_result.report_graph = Mock()
            mock_session.validate_instances.with_shacl_raw.return_value = mock_empty_result
            mock_neat.return_value = mock_session

            # Mock parsing to return no violations
            with patch(
                "cognite_data_quality._function_code.handlers.raw_validation._parse_shacl_report", return_value=[]
            ):
                with patch(
                    "cognite_data_quality._function_code.handlers.raw_validation.post_validation_results_to_records"
                ):
                    with patch(
                        "cognite_data_quality._function_code.handlers.raw_validation._delete_partition_cursor_state"
                    ):
                        result = handle_raw_validation_historic(data, mock_client)

        assert result["completed"] is True
        assert result["conforms"] is True

    def test_historic_validation_resume_from_state(self) -> None:
        """Test historic validation resuming from saved cursor state."""
        from cognite_data_quality._function_code.handlers.raw_validation import handle_raw_validation_historic

        # Mock client with saved cursor state
        mock_client = Mock()
        mock_state_node = Mock()
        mock_state_node.properties = {
            "dataQuality": {
                "FunctionValidationState/1": {
                    "syncCursor": "saved_cursor_xyz",
                    "processedCount": 100,
                    "lastUpdated": 1234567890000,
                }
            }
        }
        mock_client.data_modeling.instances.retrieve = Mock(return_value=Mock(nodes=[mock_state_node]))

        data = {
            "db_name": "test_db",
            "table_name": "test_table",
            "partition_id": "partition_0",
            # No cursor provided - should resume from saved state
            "shacl_rules": "@prefix sh: <http://www.w3.org/ns/shacl#> .",
            "records_config": {"rule_set_id": "TestRuleSet"},
        }

        with patch("cognite_data_quality._function_code.handlers.raw_validation.NeatSession") as mock_neat:
            mock_session = Mock()
            mock_empty_result = Mock()
            mock_empty_result.conforms = True
            mock_empty_result.report_graph = Mock()
            mock_session.validate_instances.with_shacl_raw.return_value = mock_empty_result
            mock_neat.return_value = mock_session

            with patch(
                "cognite_data_quality._function_code.handlers.raw_validation._parse_shacl_report", return_value=[]
            ):
                with patch(
                    "cognite_data_quality._function_code.handlers.raw_validation.post_validation_results_to_records"
                ):
                    with patch(
                        "cognite_data_quality._function_code.handlers.raw_validation._delete_partition_cursor_state"
                    ):
                        result = handle_raw_validation_historic(data, mock_client)

        assert result["completed"] is True

    def test_historic_validation_timeout_behavior(self) -> None:
        """Test historic validation handles timeout correctly."""
        from cognite_data_quality._function_code.handlers.raw_validation import handle_raw_validation_historic

        mock_client = Mock()
        mock_client.data_modeling.instances.retrieve = Mock(return_value=Mock(nodes=[]))

        data = {
            "db_name": "test_db",
            "table_name": "test_table",
            "partition_id": "partition_0",
            "cursor": "test_cursor",
            "shacl_rules": "@prefix sh: <http://www.w3.org/ns/shacl#> .",
            "records_config": {"rule_set_id": "TestRuleSet"},
        }

        # Mock time to simulate timeout
        with patch("cognite_data_quality._function_code.handlers.raw_validation.time_module") as mock_time:
            # First call returns start time, second call simulates timeout
            mock_time.time.side_effect = [
                0.0,  # start_time
                0.0,  # last_cursor_save
                500.0,  # elapsed > TIMEOUT_SECONDS (480)
            ]

            with patch("cognite_data_quality._function_code.handlers.raw_validation.NeatSession") as mock_neat:
                mock_session = Mock()
                mock_validation_result = Mock()
                mock_validation_result.conforms = True
                mock_validation_result.report_graph = Mock()
                mock_session.validate_instances.with_shacl_raw.return_value = mock_validation_result
                mock_neat.return_value = mock_session

                with patch(
                    "cognite_data_quality._function_code.handlers.raw_validation._parse_shacl_report",
                    return_value=[{"conforms": True}],
                ):
                    with patch(
                        "cognite_data_quality._function_code.handlers.raw_validation.post_validation_results_to_records"
                    ):
                        with patch(
                            "cognite_data_quality._function_code.handlers.raw_validation._save_partition_cursor_state"
                        ) as mock_save:
                            result = handle_raw_validation_historic(data, mock_client)

        # Should return incomplete with cursor for resumption
        assert result["completed"] is False
        assert "next_cursor" in result
        assert "elapsed_seconds" in result
        # Verify cursor state was saved
        mock_save.assert_called()

    def test_historic_validation_with_violations(self) -> None:
        """Test historic validation with SHACL violations."""
        from cognite_data_quality._function_code.handlers.raw_validation import handle_raw_validation_historic

        mock_client = Mock()
        mock_client.data_modeling.instances.retrieve = Mock(return_value=Mock(nodes=[]))

        data = {
            "db_name": "test_db",
            "table_name": "test_table",
            "partition_id": "partition_0",
            "cursor": "test_cursor",
            "shacl_rules": "@prefix sh: <http://www.w3.org/ns/shacl#> .",
            "records_config": {
                "stream_id": "test_stream",
                "rule_set_id": "TestRuleSet",
                "rule_set_version": "1.0",
            },
        }

        with patch("cognite_data_quality._function_code.handlers.raw_validation.NeatSession") as mock_neat:
            mock_session = Mock()
            mock_validation_result = Mock()
            mock_validation_result.conforms = False  # Validation failed
            mock_validation_result.report_graph = Mock()
            mock_session.validate_instances.with_shacl_raw.return_value = mock_validation_result
            mock_neat.return_value = mock_session

            # Mock violations
            mock_violations = [
                {"conforms": False, "instance_id": "row_1", "message": "Missing required field"},
                {"conforms": False, "instance_id": "row_2", "message": "Value out of range"},
            ]

            with patch(
                "cognite_data_quality._function_code.handlers.raw_validation._parse_shacl_report",
                return_value=mock_violations,
            ):
                with patch(
                    "cognite_data_quality._function_code.handlers.raw_validation.post_validation_results_to_records",
                    return_value=(2, []),
                ) as mock_post:
                    with patch("cognite_data_quality._neat._constants.get_raw_namespace", return_value="http://test/"):
                        with patch("cognite_data_quality._neat._extractors._raw.RAWExtractor") as mock_extractor:
                            mock_extractor.return_value._fetch_filtered_rows.return_value = []
                            with patch(
                                "cognite_data_quality._function_code.handlers.raw_validation._delete_partition_cursor_state"
                            ):
                                result = handle_raw_validation_historic(data, mock_client)

        assert result["conforms"] is False
        assert result["violations_found"] == 2
        # Verify violations were posted to Records API
        mock_post.assert_called()

    def test_historic_validation_missing_cursor(self) -> None:
        """Test historic validation fails gracefully with missing cursor."""
        from cognite_data_quality._function_code.handlers.raw_validation import handle_raw_validation_historic

        mock_client = Mock()
        mock_client.data_modeling.instances.retrieve = Mock(return_value=Mock(nodes=[]))  # No saved state

        data = {
            "db_name": "test_db",
            "table_name": "test_table",
            "partition_id": "partition_0",
            # Missing cursor and no saved state
            "shacl_rules": "@prefix sh: <http://www.w3.org/ns/shacl#> .",
            "records_config": {"rule_set_id": "TestRuleSet"},
        }

        result = handle_raw_validation_historic(data, mock_client)

        assert result["conforms"] is False
        assert "error" in result
        assert "cursor" in result["error"].lower()


class TestRawStateManagement:
    """Test state management functions for RAW validation."""

    def test_load_nonexistent_state(self) -> None:
        """Test loading state when none exists (first run)."""
        from cognite_data_quality._function_code.handlers.raw_validation import _load_raw_validation_state

        mock_client = Mock()
        mock_client.data_modeling.instances.retrieve = Mock(return_value=Mock(nodes=[]))

        state = _load_raw_validation_state(mock_client, "test_db", "test_table")

        assert state is None

    def test_save_and_load_raw_validation_state(self) -> None:
        """Test saving and loading RAW validation state."""
        from cognite_data_quality._function_code.handlers.raw_validation import (
            _save_raw_validation_state,
        )

        mock_client = Mock()

        # Save state
        _save_raw_validation_state(
            client=mock_client,
            db_name="test_db",
            table_name="test_table",
            last_processed_timestamp=1234567890000,
            rows_processed=100,
            conforms=True,
            violation_count=0,
        )

        # Verify apply was called
        mock_client.data_modeling.instances.apply.assert_called_once()

    def test_save_partition_cursor_state(self) -> None:
        """Test saving cursor state for partition resumption."""
        from cognite_data_quality._function_code.handlers.raw_validation import _save_partition_cursor_state

        mock_client = Mock()

        _save_partition_cursor_state(
            client=mock_client,
            db_name="test_db",
            table_name="test_table",
            partition_id="partition_0",
            cursor="cursor_abc123",
            processed_count=500,
            orchestration_id="orch_001",
        )

        # Verify instance was created/updated
        mock_client.data_modeling.instances.apply.assert_called_once()
        call_args = mock_client.data_modeling.instances.apply.call_args
        instance = call_args[0][0]

        assert instance.external_id == "raw_test_db_test_table_partition_0"
        assert instance.space == "dataQuality"

    def test_delete_partition_cursor_state(self) -> None:
        """Test deleting cursor state after successful completion."""
        from cognite_data_quality._function_code.handlers.raw_validation import _delete_partition_cursor_state

        mock_client = Mock()

        _delete_partition_cursor_state(
            client=mock_client,
            db_name="test_db",
            table_name="test_table",
            partition_id="partition_0",
        )

        # Verify instance was deleted
        mock_client.data_modeling.instances.delete.assert_called_once()


class TestRawValidationIntegration:
    """Integration tests for RAW validation (requires CDF connection)."""

    @pytest.mark.integration
    def test_end_to_end_incremental_validation(self, cognite_client: Any) -> None:
        """Test complete incremental validation flow with real CDF."""
        pytest.skip("Integration test - requires CDF setup")

    @pytest.mark.integration
    def test_end_to_end_historic_validation(self, cognite_client: Any) -> None:
        """Test complete historic validation flow with real CDF."""
        pytest.skip("Integration test - requires CDF setup")
