"""Tests for geometry protocol and validation."""

import numpy as np
import pytest

from panel_bie.geometry import Geometry, validate_geometry


class SimpleGeometry:
    """Simple test geometry implementing the Geometry protocol."""

    def __init__(self, n=16):
        theta = np.linspace(0, 2 * np.pi, n, endpoint=False)
        self._points = np.column_stack([np.cos(theta), np.sin(theta)])
        self._normals = self._points.copy()  # Outward for unit circle
        self._weights = np.ones(n) * 2 * np.pi / n
        self._curvature = np.ones(n)

    @property
    def points(self):
        return self._points

    @property
    def normals(self):
        return self._normals

    @property
    def weights(self):
        return self._weights

    @property
    def curvature(self):
        return self._curvature


class TestGeometryProtocol:
    """Tests for the Geometry protocol."""

    def test_simple_geometry_is_geometry(self):
        """SimpleGeometry should satisfy Geometry protocol."""
        geom = SimpleGeometry()
        assert isinstance(geom, Geometry)

    def test_validate_geometry_passes(self):
        """Valid geometry should pass validation."""
        geom = SimpleGeometry()
        validate_geometry(geom)  # Should not raise

    def test_validate_points_shape(self):
        """Should reject wrong points shape."""
        geom = SimpleGeometry()
        geom._points = np.ones((16, 3))  # Wrong second dimension

        with pytest.raises(ValueError, match="points"):
            validate_geometry(geom)

    def test_validate_normals_shape(self):
        """Should reject wrong normals shape."""
        geom = SimpleGeometry()
        geom._normals = np.ones((16, 3))

        with pytest.raises(ValueError, match="normals"):
            validate_geometry(geom)

    def test_validate_weights_shape(self):
        """Should reject wrong weights shape."""
        geom = SimpleGeometry()
        geom._weights = np.ones((16, 2))

        with pytest.raises(ValueError, match="weights"):
            validate_geometry(geom)

    def test_validate_curvature_shape(self):
        """Should reject wrong curvature shape."""
        geom = SimpleGeometry()
        geom._curvature = np.ones((16, 2))

        with pytest.raises(ValueError, match="curvature"):
            validate_geometry(geom)

    def test_validate_normals_unit(self):
        """Should reject non-unit normals."""
        geom = SimpleGeometry()
        geom._normals = geom._normals * 2  # Not unit

        with pytest.raises(ValueError, match="unit vectors"):
            validate_geometry(geom)

    def test_validate_weights_positive(self):
        """Should reject non-positive weights."""
        geom = SimpleGeometry()
        geom._weights[0] = -1.0

        with pytest.raises(ValueError, match="positive"):
            validate_geometry(geom)

    def test_validate_weights_zero(self):
        """Should reject zero weights."""
        geom = SimpleGeometry()
        geom._weights[5] = 0.0

        with pytest.raises(ValueError, match="positive"):
            validate_geometry(geom)

    def test_validate_mismatched_sizes(self):
        """Should reject mismatched array sizes."""
        geom = SimpleGeometry(n=16)
        geom._normals = np.column_stack([
            np.cos(np.linspace(0, 2 * np.pi, 20)),
            np.sin(np.linspace(0, 2 * np.pi, 20)),
        ])

        with pytest.raises(ValueError, match="normals"):
            validate_geometry(geom)
