"""Tests for Laplace solver."""

import numpy as np
import pytest


class TestDoubleLayerLaplace:
    """Tests for the DoubleLayerLaplace solver."""

    @pytest.fixture
    def circle_geometry(self):
        """Create a unit circle discretization for testing."""
        # Simple circle discretization
        n = 64
        theta = np.linspace(0, 2 * np.pi, n, endpoint=False)

        class SimpleGeometry:
            points = np.column_stack([np.cos(theta), np.sin(theta)])
            normals = points.copy()
            weights = np.ones(n) * 2 * np.pi / n
            curvature = np.ones(n)

        return SimpleGeometry()

    def test_constant_boundary_data(self, circle_geometry):
        """Constant boundary data should give constant solution."""
        from panel_bie.laplace import DoubleLayerLaplace

        solver = DoubleLayerLaplace(circle_geometry)

        # u = 1 on boundary
        g = np.ones(len(circle_geometry.points))
        mu = solver.solve(g)

        # Evaluate at origin
        u_origin = solver.evaluate(mu, np.array([0.0, 0.0]))

        # Should be 1 everywhere inside
        assert u_origin == pytest.approx(1.0, rel=1e-6)

    def test_linear_boundary_data(self, circle_geometry):
        """Linear boundary data u=x should give u=x inside."""
        from panel_bie.laplace import DoubleLayerLaplace

        solver = DoubleLayerLaplace(circle_geometry)

        # u = x on boundary
        g = circle_geometry.points[:, 0]
        mu = solver.solve(g)

        # Test at several interior points
        test_points = np.array([
            [0.0, 0.0],
            [0.3, 0.0],
            [0.0, 0.4],
            [-0.2, 0.1],
        ])

        for p in test_points:
            u = solver.evaluate(mu, p)
            expected = p[0]  # u = x
            assert u == pytest.approx(expected, rel=1e-4)

    def test_quadratic_boundary_data(self, circle_geometry):
        """Test with u = x² - y² (harmonic)."""
        from panel_bie.laplace import DoubleLayerLaplace

        solver = DoubleLayerLaplace(circle_geometry)

        # u = x² - y² on boundary
        pts = circle_geometry.points
        g = pts[:, 0]**2 - pts[:, 1]**2
        mu = solver.solve(g)

        # Test at interior points
        test_points = np.array([
            [0.0, 0.0],
            [0.3, 0.2],
            [-0.4, 0.1],
        ])

        for p in test_points:
            u = solver.evaluate(mu, p)
            expected = p[0]**2 - p[1]**2
            assert u == pytest.approx(expected, rel=1e-3)

    def test_solver_callable_boundary(self, circle_geometry):
        """Solver should accept callable for boundary data."""
        from panel_bie.laplace import DoubleLayerLaplace

        solver = DoubleLayerLaplace(circle_geometry)

        def boundary_func(x, y):
            return x + 2 * y

        mu = solver.solve(boundary_func)

        # Should work and give reasonable result
        u = solver.evaluate(mu, np.array([0.0, 0.0]))
        assert u == pytest.approx(0.0, abs=1e-6)

        u = solver.evaluate(mu, np.array([0.25, 0.25]))
        assert u == pytest.approx(0.75, rel=1e-3)
