"""Unit tests for IWFM readers using the committed small_model fixtures.

These tests exercise the IO readers against real IWFM-format files on disk,
complementing existing unit tests that use synthetic in-memory data.

The fixture files are generated by ``tests/fixtures/generate_small_model.py``
from the tutorial model (21x21 grid, 2 layers, 3 stream reaches, 1 lake,
7 crop types).
"""

from __future__ import annotations

from pathlib import Path

import pytest


@pytest.fixture
def pp_main(small_model_path: Path) -> Path:
    """Path to the preprocessor main file."""
    return small_model_path / "Preprocessor" / "Preprocessor.in"


@pytest.fixture
def sim_main(small_model_path: Path) -> Path:
    """Path to the simulation main file."""
    return small_model_path / "Simulation" / "Simulation_MAIN.IN"


# ---- guard: skip all tests if fixture files weren't generated ----


@pytest.fixture(autouse=True)
def _require_fixtures(small_model_path: Path) -> None:
    pp = small_model_path / "Preprocessor" / "Preprocessor.in"
    if not pp.exists():
        pytest.skip("small_model fixtures not generated")


# =============================================================================
# Preprocessor main file
# =============================================================================


class TestPreprocessorMain:
    def test_parse_preprocessor_main(self, pp_main: Path) -> None:
        """Parse preprocessor main file and verify resolved file paths."""
        from pyiwfm.io.preprocessor import read_preprocessor_main

        config = read_preprocessor_main(pp_main)

        assert config.nodes_file is not None
        assert config.elements_file is not None
        assert config.stratigraphy_file is not None
        # Paths should be absolute after resolution
        assert config.nodes_file.is_absolute()
        assert config.nodes_file.exists()
        assert config.elements_file.exists()
        assert config.stratigraphy_file.exists()


# =============================================================================
# Node file
# =============================================================================


class TestNodeReader:
    def test_read_nodes(self, small_model_path: Path) -> None:
        """Read nodes.dat and verify 441 nodes with correct coordinates."""
        from pyiwfm.io.ascii import read_nodes

        nodes_file = small_model_path / "Preprocessor" / "Nodes.dat"
        nodes = read_nodes(nodes_file)

        assert len(nodes) == 441

        # First node: (550000, 4400000)
        assert nodes[1].x == pytest.approx(550_000.0)
        assert nodes[1].y == pytest.approx(4_400_000.0)

        # Last node (441): 21x21 grid, last row/col
        assert nodes[441].x == pytest.approx(550_000.0 + 20 * 2_000.0)
        assert nodes[441].y == pytest.approx(4_400_000.0 + 20 * 2_000.0)

    def test_node_ids_are_one_based(self, small_model_path: Path) -> None:
        from pyiwfm.io.ascii import read_nodes

        nodes = read_nodes(small_model_path / "Preprocessor" / "Nodes.dat")
        assert 1 in nodes
        assert 441 in nodes
        assert 0 not in nodes


# =============================================================================
# Element file
# =============================================================================


class TestElementReader:
    def test_read_elements(self, small_model_path: Path) -> None:
        """Read elements.dat and verify 400 elements with subregion assignments."""
        from pyiwfm.io.ascii import read_elements

        elem_file = small_model_path / "Preprocessor" / "Elements.dat"
        elements, n_subregions, subregion_names = read_elements(elem_file)

        assert len(elements) == 400
        assert n_subregions == 2
        assert 1 in subregion_names
        assert 2 in subregion_names

    def test_element_subregions(self, small_model_path: Path) -> None:
        """South half (rows 0-9) = subregion 1, north half = subregion 2."""
        from pyiwfm.io.ascii import read_elements

        elements, _, _ = read_elements(small_model_path / "Preprocessor" / "Elements.dat")
        # Element 1 is in first row → subregion 1
        assert elements[1].subregion == 1
        # Element 201 is in row 10 → subregion 2
        assert elements[201].subregion == 2

    def test_element_vertices(self, small_model_path: Path) -> None:
        """First element should be a quad with 4 vertices."""
        from pyiwfm.io.ascii import read_elements

        elements, _, _ = read_elements(small_model_path / "Preprocessor" / "Elements.dat")
        verts = elements[1].vertices
        assert len(verts) == 4
        # First element in a 21-col grid: (1, 2, 23, 22)
        assert verts == (1, 2, 23, 22)


# =============================================================================
# Stratigraphy file
# =============================================================================


class TestStratigraphyReader:
    def test_read_stratigraphy(self, small_model_path: Path) -> None:
        """Read stratigraphy.dat and verify 2 layers, 441 nodes."""
        from pyiwfm.io.ascii import read_stratigraphy

        strat = read_stratigraphy(small_model_path / "Preprocessor" / "Stratigraphy.dat")

        assert strat.n_layers == 2
        assert strat.n_nodes == 441
        assert strat.gs_elev.shape == (441,)
        assert strat.top_elev.shape == (441, 2)
        assert strat.bottom_elev.shape == (441, 2)

    def test_ground_surface_elevations(self, small_model_path: Path) -> None:
        """Most nodes have gs_elev=500, lake-bed nodes are lower."""
        from pyiwfm.io.ascii import read_stratigraphy

        strat = read_stratigraphy(small_model_path / "Preprocessor" / "Stratigraphy.dat")

        # Node 1 (non-lake) should be 500
        assert strat.gs_elev[0] == pytest.approx(500.0)
        # Node 179 (lake-bed depression) should be 250
        assert strat.gs_elev[178] == pytest.approx(250.0)


# =============================================================================
# Groundwater main file
# =============================================================================


class TestGWMainReader:
    def test_read_gw_main(self, small_model_path: Path) -> None:
        """Read GW_MAIN.dat and verify version and sub-file paths."""
        from pyiwfm.io.groundwater import read_gw_main_file

        gw_file = small_model_path / "Simulation" / "GW" / "GW_MAIN.dat"
        config = read_gw_main_file(gw_file)

        assert config.version == "4.0"
        assert config.bc_file is not None
        assert config.tile_drain_file is not None
        assert config.pumping_file is not None


# =============================================================================
# Stream main file
# =============================================================================


class TestStreamMainReader:
    def test_read_stream_main(self, small_model_path: Path) -> None:
        """Read Stream_MAIN.dat and verify file references."""
        from pyiwfm.io.streams import read_stream_main_file

        stream_file = small_model_path / "Simulation" / "Stream" / "Stream_MAIN.dat"
        config = read_stream_main_file(stream_file)

        assert config.version == "4.0"
        assert config.diversion_spec_file is not None
        assert config.bypass_spec_file is not None
        # 23 hydrograph output locations (one per stream node)
        assert config.hydrograph_count == 23


# =============================================================================
# Lake main file
# =============================================================================


class TestLakeMainReader:
    def test_read_lake_main(self, small_model_path: Path) -> None:
        """Read Lake_MAIN.dat and verify 1 lake."""
        from pyiwfm.io.lakes import read_lake_main_file

        lake_file = small_model_path / "Simulation" / "Lake" / "Lake_MAIN.dat"
        config = read_lake_main_file(lake_file)

        assert config.version == "4.0"
        assert config.max_elev_file is not None
        # 1 lake defined
        assert len(config.lake_params) == 1
        assert config.lake_params[0].name == "Sample Lake"


# =============================================================================
# Root zone main file
# =============================================================================


class TestRootZoneMainReader:
    def test_read_rootzone_main(self, small_model_path: Path) -> None:
        """Read RootZone_MAIN.dat and verify version and sub-file paths."""
        from pyiwfm.io.rootzone import read_rootzone_main_file

        rz_file = small_model_path / "Simulation" / "RootZone" / "RootZone_MAIN.dat"
        config = read_rootzone_main_file(rz_file)

        assert config.version == "4.12"
        assert config.return_flow_file is not None
        assert config.reuse_file is not None
        assert config.irrigation_period_file is not None
        assert config.convergence_tolerance == pytest.approx(0.001)


# =============================================================================
# Load from preprocessor (mesh + stratigraphy)
# =============================================================================


class TestLoadFromPreprocessor:
    def test_load_model_from_preprocessor(self, pp_main: Path) -> None:
        """Full preprocessor load: mesh + stratigraphy."""
        from pyiwfm.core.model import IWFMModel

        model = IWFMModel.from_preprocessor(pp_main)

        assert model.mesh is not None
        assert model.mesh.n_nodes == 441
        assert model.mesh.n_elements == 400
        assert len(model.mesh.subregions) == 2
        assert model.stratigraphy is not None
        assert model.stratigraphy.n_layers == 2


# =============================================================================
# Full model load from simulation file
# =============================================================================


class TestLoadCompleteModel:
    def test_load_complete_model(self, sim_main: Path, pp_main: Path) -> None:
        """Load entire model from simulation + preprocessor files."""
        from pyiwfm.io.model_loader import CompleteModelLoader

        loader = CompleteModelLoader(
            simulation_file=sim_main,
            preprocessor_file=pp_main,
        )
        result = loader.load()

        assert result.success, f"Model load failed: {result.errors}"
        model = result.model
        assert model is not None

        # Mesh
        assert model.mesh is not None
        assert model.mesh.n_nodes == 441
        assert model.mesh.n_elements == 400

        # Stratigraphy
        assert model.stratigraphy is not None
        assert model.stratigraphy.n_layers == 2
