"""Independent checks for the CPU reconstruction of Legacy geometry flags."""
from __future__ import annotations

import ast
import importlib
from pathlib import Path

import numpy as np
import pytest

from OID_analysis.configs import CENTER_Y, L0, NX, NY, get_scene
from OID_analysis.v2.geometry import build_legacy_geometry, crop_stride_metric


def _legacy_disk(center, radius):
    expected = set()
    x_c, y_c = center
    for x in range(int(x_c - radius) - 1, int(x_c + radius) + 1):
        for y in range(int(y_c - radius) - 1, int(y_c + radius) + 1):
            if (x - x_c) ** 2 + (y - y_c) ** 2 < radius**2:
                expected.add((x, y))
    return expected


@pytest.mark.parametrize("scene", ["karman_re100", "illusion_1.0L"])
def test_boundaries_axis_orientation_and_pinball_rasterization(scene):
    mask, metadata = build_legacy_geometry(scene)
    cfg = get_scene(scene)
    assert mask.dtype == np.bool_ and mask.shape == (NX, NY)
    assert metadata["field_axes"] == ["x", "y"]
    assert not mask[0, :].any() and not mask[-1, :].any()
    assert not mask[:, 0].any() and not mask[:, -1].any()

    cylinders = {obj["name"]: obj for obj in metadata["objects"] if obj["kind"] == "cylinder"}
    expected = {
        "front": ((cfg["pinball_front_x"] * L0, CENTER_Y), L0 / 2),
        "upper": ((cfg["pinball_rear_x"] * L0, CENTER_Y + 0.75 * L0), L0 / 2),
        "lower": ((cfg["pinball_rear_x"] * L0, CENTER_Y - 0.75 * L0), L0 / 2),
    }
    for name, (center, radius) in expected.items():
        assert cylinders[name]["center"][:2] == list(center)
        assert cylinders[name]["radius"] == radius
        for x, y in _legacy_disk(center, radius):
            assert not mask[x, y]
        # Strict inequality leaves exact-radius integer points fluid when present.
        x = int(center[0] + radius)
        assert mask[x, int(center[1] - 0.5)]


def test_sensor_disks_remain_fluid():
    for scene in ("karman_re100", "illusion_1.0L"):
        mask, metadata = build_legacy_geometry(scene)
        sensors = [obj for obj in metadata["objects"] if obj["kind"] == "sensor"]
        assert [obj["name"] for obj in sensors] == ["sensor_upper", "sensor_center", "sensor_lower"]
        for sensor in sensors:
            assert all(mask[x, y] for x, y in _legacy_disk(sensor["center"][:2], sensor["radius"]))


def test_karman_has_configured_disturbance_and_scenes_differ():
    karman, k_meta = build_legacy_geometry("karman_re100")
    illusion, i_meta = build_legacy_geometry("illusion_1.0L")
    disturbance = [obj for obj in k_meta["objects"] if obj["name"] == "disturbance"]
    assert len(disturbance) == 1
    assert disturbance[0]["center"][:2] == [10.0 * L0, CENTER_Y]
    assert disturbance[0]["radius"] == L0
    assert not karman[int(10 * L0), int(CENTER_Y)]
    assert illusion[int(10 * L0), int(CENTER_Y)]
    assert not any(obj["name"] == "disturbance" for obj in i_meta["objects"])
    assert not np.array_equal(karman, illusion)


def test_crop_stride_metric_uses_actual_mask_and_declares_contract():
    mask, _ = build_legacy_geometry("karman_re100")
    crop = (slice(190, 231), slice(235, 278))
    sampled, metric, metadata = crop_stride_metric(mask, crop=crop, stride=3, lattice_cell_area=0.25)
    expected = mask[190:231, 235:278][::3, ::3]
    assert sampled.shape == (14, 15)
    assert np.array_equal(sampled, expected)
    assert np.all(metric[~sampled] == 0.0)
    assert np.all(metric[sampled] == 2.25)
    assert metadata["field_axes"] == ["x", "y"]
    assert metadata["component_order"] == ["ux", "uy"]
    assert metadata["lattice_cell_area"] == 0.25
    assert metadata["sampled_cell_area"] == 2.25


@pytest.mark.parametrize("kwargs", [
    {"field_axes": ("y", "x")},
    {"crop": (slice(-1, 3), slice(0, 3))},
    {"crop": (slice(0, 3, 2), slice(0, 3))},
    {"stride": 0},
])
def test_crop_stride_metric_rejects_axis_crop_and_stride_errors(kwargs):
    with pytest.raises(ValueError):
        crop_stride_metric(np.ones((5, 6), dtype=bool), **kwargs)


def test_geometry_module_has_no_pycuda_import():
    module_path = Path(importlib.import_module("OID_analysis.v2.geometry").__file__)
    tree = ast.parse(module_path.read_text(encoding="utf-8"))
    imported = {
        alias.name.split(".")[0]
        for node in ast.walk(tree)
        if isinstance(node, ast.Import)
        for alias in node.names
    }
    imported.update(
        node.module.split(".")[0]
        for node in ast.walk(tree)
        if isinstance(node, ast.ImportFrom) and node.module
    )
    assert "pycuda" not in imported


@pytest.mark.parametrize(("scene", "fluid", "solid"), [
    ("karman_re100", 5025, 95),
    ("illusion_1.0L", 5024, 96),
])
def test_phase3_exact_reduced_mask_counts(scene, fluid, solid):
    full, _ = build_legacy_geometry(scene)
    reduced, metric, metadata = crop_stride_metric(
        full, crop=(slice(360, 1000), slice(None)), stride=8
    )
    assert reduced.shape == (80, 64)
    assert (int(reduced.sum()), int((~reduced).sum())) == (fluid, solid)
    assert np.all(metric[reduced] == 64.0) and np.all(metric[~reduced] == 0.0)
    assert metadata["sampled_cell_area"] == 64.0
