"""Tests for geofence boundary enforcer."""

import pytest
from nav_msgs.msg import Odometry
from geometry_msgs.msg import Point, Quaternion, Pose, PoseWithCovariance, TwistWithCovariance
import rclpy
from rclpy.time import Time

from saltybot_geofence.geofence_node import GeofenceNode


@pytest.fixture
def rclpy_fixture():
    rclpy.init()
    yield
    rclpy.shutdown()


@pytest.fixture
def node(rclpy_fixture):
    node = GeofenceNode()
    yield node
    node.destroy_node()


class TestInit:
    def test_node_initialization(self, node):
        assert node.enforce_boundary is False
        assert node.margin == 0.0
        assert node.inside_geofence is True


class TestVertexParsing:
    def test_parse_vertices_valid(self, node):
        vertices = node._parse_vertices([0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0])
        assert len(vertices) == 4
        assert vertices[0] == (0.0, 0.0)
        assert vertices[1] == (1.0, 0.0)

    def test_parse_vertices_insufficient(self, node):
        vertices = node._parse_vertices([0.0, 0.0, 1.0])
        assert len(vertices) == 0


class TestPointInPolygon:
    def test_point_inside_square(self, node):
        vertices = [(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0)]
        assert node._point_in_polygon((1.0, 1.0), vertices) is True

    def test_point_outside_square(self, node):
        vertices = [(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0)]
        assert node._point_in_polygon((3.0, 1.0), vertices) is False

    def test_point_on_vertex(self, node):
        vertices = [(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0)]
        # Point on vertex behavior may vary (typically outside)
        result = node._point_in_polygon((0.0, 0.0), vertices)
        assert isinstance(result, bool)

    def test_point_on_edge(self, node):
        vertices = [(0.0, 0.0), (2.0, 0.0), (2.0, 2.0), (0.0, 2.0)]
        # Point on edge behavior (typically outside)
        result = node._point_in_polygon((1.0, 0.0), vertices)
        assert isinstance(result, bool)

    def test_triangle_inside(self, node):
        vertices = [(0.0, 0.0), (4.0, 0.0), (2.0, 3.0)]
        assert node._point_in_polygon((2.0, 1.0), vertices) is True

    def test_triangle_outside(self, node):
        vertices = [(0.0, 0.0), (4.0, 0.0), (2.0, 3.0)]
        assert node._point_in_polygon((5.0, 5.0), vertices) is False

    def test_concave_polygon(self, node):
        # L-shaped polygon
        vertices = [(0.0, 0.0), (3.0, 0.0), (3.0, 1.0), (1.0, 1.0), (1.0, 3.0), (0.0, 3.0)]
        assert node._point_in_polygon((0.5, 0.5), vertices) is True
        assert node._point_in_polygon((2.0, 2.0), vertices) is False

    def test_circle_approximation(self, node):
        # Octagon approximating circle
        import math
        vertices = []
        for i in range(8):
            angle = 2 * math.pi * i / 8
            vertices.append((math.cos(angle), math.sin(angle)))

        # Center should be inside
        assert node._point_in_polygon((0.0, 0.0), vertices) is True
        # Far outside should be outside
        assert node._point_in_polygon((10.0, 10.0), vertices) is False


class TestOdometryProcessing:
    def test_odometry_update_position(self, node):
        node.geofence_vertices = [(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)]

        msg = Odometry()
        msg.pose.pose.position.x = 5.0
        msg.pose.pose.position.y = 5.0

        node._on_odometry(msg)

        assert node.robot_x == 5.0
        assert node.robot_y == 5.0

    def test_breach_detection_inside(self, node):
        node.geofence_vertices = [(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)]

        msg = Odometry()
        msg.pose.pose.position.x = 5.0
        msg.pose.pose.position.y = 5.0

        node._on_odometry(msg)

        assert node.inside_geofence is True

    def test_breach_detection_outside(self, node):
        node.geofence_vertices = [(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)]

        msg = Odometry()
        msg.pose.pose.position.x = 15.0
        msg.pose.pose.position.y = 5.0

        node._on_odometry(msg)

        assert node.inside_geofence is False

    def test_breach_flag_transition(self, node):
        node.geofence_vertices = [(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)]
        assert node.breach_published is False

        # Move outside
        msg = Odometry()
        msg.pose.pose.position.x = 15.0
        msg.pose.pose.position.y = 5.0
        node._on_odometry(msg)

        assert node.breach_published is True

        # Move back inside
        msg.pose.pose.position.x = 5.0
        msg.pose.pose.position.y = 5.0
        node._on_odometry(msg)

        assert node.breach_published is False


class TestEdgeCases:
    def test_no_geofence_defined(self, node):
        node.geofence_vertices = []

        msg = Odometry()
        msg.pose.pose.position.x = 0.0
        msg.pose.pose.position.y = 0.0

        node._on_odometry(msg)

        # Should default to safe (inside)
        assert node.inside_geofence is True

    def test_very_small_polygon(self, node):
        vertices = [(0.0, 0.0), (0.01, 0.0), (0.01, 0.01), (0.0, 0.01)]
        assert node._point_in_polygon((0.005, 0.005), vertices) is True
        assert node._point_in_polygon((0.1, 0.1), vertices) is False

    def test_large_coordinates(self, node):
        vertices = [(0.0, 0.0), (1000.0, 0.0), (1000.0, 1000.0), (0.0, 1000.0)]
        assert node._point_in_polygon((500.0, 500.0), vertices) is True
        assert node._point_in_polygon((1500.0, 500.0), vertices) is False
