#!/usr/bin/env python3
"""
Test Suite: Cursor Bounds Validation (UVS-H43)
==============================================
Tests for cursor movement boundary validation.

VERIFICATION_STAMP
Story: UVS-H43
Verified By: Claude Opus 4.5
Verified At: 2026-02-03
"""

import sys
import pytest

sys.path.insert(0, '/mnt/e/genesis-system')

from core.security.input_validator import (
    validate_coordinates,
    validate_tool_args,
    ValidationError,
    MAX_VIEWPORT_WIDTH,
    MAX_VIEWPORT_HEIGHT,
    CURSOR_TOOL_SCHEMA,
    ZOOM_TOOL_SCHEMA
)


class TestCursorBoundsValidation:
    """Black box tests for cursor boundary validation."""

    def test_valid_center_screen(self):
        """Coordinates in center of screen are valid."""
        result = validate_coordinates(960, 540)
        assert result.valid
        assert result.value == (960, 540)

    def test_valid_top_left_corner(self):
        """Top-left corner (0,0) is valid."""
        result = validate_coordinates(0, 0)
        assert result.valid
        assert result.value == (0, 0)

    def test_valid_bottom_right_corner(self):
        """Bottom-right corner is valid."""
        result = validate_coordinates(MAX_VIEWPORT_WIDTH, MAX_VIEWPORT_HEIGHT)
        assert result.valid
        assert result.value == (MAX_VIEWPORT_WIDTH, MAX_VIEWPORT_HEIGHT)

    def test_negative_x_clamped_to_zero(self):
        """Negative X coordinate is clamped to 0."""
        result = validate_coordinates(-100, 500)
        assert not result.valid
        assert result.value[0] == 0
        assert result.value[1] == 500
        assert "x=-100" in result.error

    def test_negative_y_clamped_to_zero(self):
        """Negative Y coordinate is clamped to 0."""
        result = validate_coordinates(500, -100)
        assert not result.valid
        assert result.value[0] == 500
        assert result.value[1] == 0

    def test_both_negative_clamped(self):
        """Both negative coordinates are clamped."""
        result = validate_coordinates(-1000, -1000)
        assert not result.valid
        assert result.value == (0, 0)

    def test_x_beyond_viewport_clamped(self):
        """X beyond max viewport is clamped."""
        result = validate_coordinates(5000, 500)
        assert not result.valid
        assert result.value[0] == MAX_VIEWPORT_WIDTH
        assert result.value[1] == 500

    def test_y_beyond_viewport_clamped(self):
        """Y beyond max viewport is clamped."""
        result = validate_coordinates(500, 3000)
        assert not result.valid
        assert result.value[0] == 500
        assert result.value[1] == MAX_VIEWPORT_HEIGHT

    def test_custom_viewport_bounds(self):
        """Custom viewport bounds are respected."""
        result = validate_coordinates(200, 200, max_width=100, max_height=100)
        assert not result.valid
        assert result.value == (100, 100)


class TestCursorToolArgsValidation:
    """Tests for complete cursor tool argument validation."""

    def test_valid_cursor_args(self):
        """Valid cursor tool arguments pass."""
        args = {"x": 100, "y": 200}
        result = validate_tool_args(args, CURSOR_TOOL_SCHEMA)
        assert result["x"] == 100
        assert result["y"] == 200

    def test_string_coordinates_converted(self):
        """String coordinates are converted to integers."""
        args = {"x": "100", "y": "200"}
        result = validate_tool_args(args, CURSOR_TOOL_SCHEMA)
        assert result["x"] == 100
        assert result["y"] == 200

    def test_float_coordinates_truncated(self):
        """Float coordinates are truncated to integers."""
        args = {"x": 100.7, "y": 200.3}
        result = validate_tool_args(args, CURSOR_TOOL_SCHEMA)
        assert result["x"] == 100
        assert result["y"] == 200

    def test_missing_x_raises(self):
        """Missing X coordinate raises error."""
        args = {"y": 200}
        with pytest.raises(ValidationError):
            validate_tool_args(args, CURSOR_TOOL_SCHEMA)

    def test_missing_y_raises(self):
        """Missing Y coordinate raises error."""
        args = {"x": 100}
        with pytest.raises(ValidationError):
            validate_tool_args(args, CURSOR_TOOL_SCHEMA)

    def test_invalid_x_type_raises(self):
        """Invalid X type raises error."""
        args = {"x": "not_a_number", "y": 200}
        with pytest.raises(ValidationError):
            validate_tool_args(args, CURSOR_TOOL_SCHEMA)


class TestZoomToolBounds:
    """Tests for zoom viewport bounds validation."""

    def test_valid_zoom_args(self):
        """Valid zoom arguments pass."""
        args = {"x": 100, "y": 100, "width": 400, "height": 400}
        result = validate_tool_args(args, ZOOM_TOOL_SCHEMA)
        assert result["x"] == 100
        assert result["y"] == 100
        assert result["width"] == 400
        assert result["height"] == 400

    def test_default_zoom_dimensions(self):
        """Missing dimensions use defaults."""
        args = {"x": 100, "y": 100}
        result = validate_tool_args(args, ZOOM_TOOL_SCHEMA)
        assert result["width"] == 400
        assert result["height"] == 400

    def test_zoom_width_clamped(self):
        """Zoom width is clamped to max."""
        args = {"x": 100, "y": 100, "width": 3000, "height": 400}
        result = validate_tool_args(args, ZOOM_TOOL_SCHEMA)
        assert result["width"] == 1920  # Max width

    def test_zoom_height_clamped(self):
        """Zoom height is clamped to max."""
        args = {"x": 100, "y": 100, "width": 400, "height": 2000}
        result = validate_tool_args(args, ZOOM_TOOL_SCHEMA)
        assert result["height"] == 1080  # Max height

    def test_zoom_min_dimensions(self):
        """Zoom dimensions have minimum size."""
        args = {"x": 100, "y": 100, "width": 10, "height": 10}
        result = validate_tool_args(args, ZOOM_TOOL_SCHEMA)
        assert result["width"] >= 100  # Min width
        assert result["height"] >= 100  # Min height


class TestEdgeCases:
    """Edge case tests for cursor bounds."""

    def test_zero_is_valid(self):
        """Zero coordinates are valid."""
        result = validate_coordinates(0, 0)
        assert result.valid

    def test_max_int_clamped(self):
        """Very large integers are clamped."""
        result = validate_coordinates(2**31, 2**31)
        assert result.value[0] == MAX_VIEWPORT_WIDTH
        assert result.value[1] == MAX_VIEWPORT_HEIGHT

    def test_negative_one(self):
        """Negative one is clamped to zero."""
        result = validate_coordinates(-1, -1)
        assert result.value == (0, 0)

    def test_boundary_values(self):
        """Boundary values are handled correctly."""
        # Just inside bounds
        result = validate_coordinates(MAX_VIEWPORT_WIDTH - 1, MAX_VIEWPORT_HEIGHT - 1)
        assert result.valid

        # Exactly at bounds
        result = validate_coordinates(MAX_VIEWPORT_WIDTH, MAX_VIEWPORT_HEIGHT)
        assert result.valid

        # Just outside bounds
        result = validate_coordinates(MAX_VIEWPORT_WIDTH + 1, MAX_VIEWPORT_HEIGHT + 1)
        assert not result.valid


if __name__ == '__main__':
    pytest.main([__file__, '-v', '--tb=short'])
