"""Unit tests for groundwater subsidence I/O.

Tests:
- GroundwaterReader.read_subsidence()
- Subsidence write -> read roundtrip
- read_subsidence convenience function
"""

from __future__ import annotations

from pathlib import Path

import pytest

from pyiwfm.components.groundwater import (
    AppGW,
    Subsidence,
)
from pyiwfm.core.exceptions import FileFormatError
from pyiwfm.io.groundwater import (
    GroundwaterReader,
    GroundwaterWriter,
    GWFileConfig,
    read_subsidence,
)

# =============================================================================
# Test read_subsidence method
# =============================================================================


class TestReadSubsidence:
    """Tests for GroundwaterReader.read_subsidence()."""

    def test_read_basic(self, tmp_path: Path) -> None:
        """Test reading a basic subsidence file."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  Subsidence parameters file
C  Generated by pyiwfm
C
C  ELEM  LAYER  ELASTIC_S  INELASTIC_S  PRECON_HEAD
3                              / N_SUBSIDENCE
    1     1   1.000000e-05   1.000000e-04      90.0000
    2     1   2.000000e-05   2.000000e-04      85.0000
    3     2   3.000000e-05   3.000000e-04      80.0000
""")

        reader = GroundwaterReader()
        subsidence = reader.read_subsidence(filepath)

        assert len(subsidence) == 3
        assert subsidence[0].element == 1
        assert subsidence[0].layer == 1
        assert subsidence[0].elastic_storage == pytest.approx(1e-5)
        assert subsidence[0].inelastic_storage == pytest.approx(1e-4)
        assert subsidence[0].preconsolidation_head == pytest.approx(90.0)

        assert subsidence[1].element == 2
        assert subsidence[1].elastic_storage == pytest.approx(2e-5)
        assert subsidence[1].preconsolidation_head == pytest.approx(85.0)

        assert subsidence[2].element == 3
        assert subsidence[2].layer == 2
        assert subsidence[2].elastic_storage == pytest.approx(3e-5)

    def test_read_single_element(self, tmp_path: Path) -> None:
        """Test reading subsidence for single element."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  Subsidence file
1                              / N_SUBSIDENCE
    5     3   5.500000e-06   8.200000e-05     120.5000
""")

        reader = GroundwaterReader()
        subsidence = reader.read_subsidence(filepath)

        assert len(subsidence) == 1
        assert subsidence[0].element == 5
        assert subsidence[0].layer == 3
        assert subsidence[0].elastic_storage == pytest.approx(5.5e-6)
        assert subsidence[0].inelastic_storage == pytest.approx(8.2e-5)
        assert subsidence[0].preconsolidation_head == pytest.approx(120.5)

    def test_read_multi_layer(self, tmp_path: Path) -> None:
        """Test reading subsidence for same element across multiple layers."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  Multi-layer subsidence
4                              / N_SUBSIDENCE
   10     1   1.000000e-05   1.000000e-04      90.0000
   10     2   2.000000e-05   2.000000e-04      85.0000
   10     3   3.000000e-05   3.000000e-04      80.0000
   10     4   4.000000e-05   4.000000e-04      75.0000
""")

        reader = GroundwaterReader()
        subsidence = reader.read_subsidence(filepath)

        assert len(subsidence) == 4
        assert all(s.element == 10 for s in subsidence)
        assert [s.layer for s in subsidence] == [1, 2, 3, 4]
        assert subsidence[3].elastic_storage == pytest.approx(4e-5)

    def test_read_with_various_comments(self, tmp_path: Path) -> None:
        """Test reading with C, c, * comment styles."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  Uppercase C comment
c  lowercase c comment
*  asterisk comment

1                              / N_SUBSIDENCE
C  data starts here
    1     1   1.000000e-05   1.000000e-04      90.0000
""")

        reader = GroundwaterReader()
        subsidence = reader.read_subsidence(filepath)

        assert len(subsidence) == 1
        assert subsidence[0].element == 1

    def test_read_missing_n_subsidence(self, tmp_path: Path) -> None:
        """Test error when N_SUBSIDENCE is missing."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  Only comments
C  No data
""")

        reader = GroundwaterReader()
        with pytest.raises(FileFormatError, match="Could not find N_SUBSIDENCE"):
            reader.read_subsidence(filepath)

    def test_read_invalid_n_subsidence(self, tmp_path: Path) -> None:
        """Test error when N_SUBSIDENCE is not a number."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  Subsidence file
abc                            / N_SUBSIDENCE
""")

        reader = GroundwaterReader()
        with pytest.raises(FileFormatError, match="Invalid N_SUBSIDENCE"):
            reader.read_subsidence(filepath)

    def test_read_invalid_data_row(self, tmp_path: Path) -> None:
        """Test error when data row has non-numeric values."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  Subsidence file
1                              / N_SUBSIDENCE
abc  def  ghi  jkl  mno
""")

        reader = GroundwaterReader()
        with pytest.raises(FileFormatError, match="Invalid subsidence data"):
            reader.read_subsidence(filepath)

    def test_read_zero_count(self, tmp_path: Path) -> None:
        """Test reading subsidence file with count of 0."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  No subsidence
0                              / N_SUBSIDENCE
""")

        reader = GroundwaterReader()
        subsidence = reader.read_subsidence(filepath)

        assert len(subsidence) == 0

    def test_read_scientific_notation(self, tmp_path: Path) -> None:
        """Test reading subsidence with various scientific notation forms."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  Scientific notation test
2                              / N_SUBSIDENCE
    1     1   1.0E-05   1.0E-04      90.0
    2     1   1.5e-06   2.3e-03     100.0
""")

        reader = GroundwaterReader()
        subsidence = reader.read_subsidence(filepath)

        assert len(subsidence) == 2
        assert subsidence[0].elastic_storage == pytest.approx(1.0e-5)
        assert subsidence[0].inelastic_storage == pytest.approx(1.0e-4)
        assert subsidence[1].elastic_storage == pytest.approx(1.5e-6)
        assert subsidence[1].inelastic_storage == pytest.approx(2.3e-3)


# =============================================================================
# Test Subsidence Roundtrip
# =============================================================================


class TestSubsidenceRoundtrip:
    """Tests for subsidence write -> read roundtrip."""

    def test_roundtrip_single(self, tmp_path: Path) -> None:
        """Test write/read roundtrip with single subsidence."""
        gw = AppGW(n_nodes=10, n_layers=2, n_elements=5)
        gw.add_subsidence(
            Subsidence(
                element=3,
                layer=1,
                elastic_storage=1.5e-5,
                inelastic_storage=2.0e-4,
                preconsolidation_head=95.0,
            )
        )

        config = GWFileConfig(output_dir=tmp_path)
        writer = GroundwaterWriter(config)
        filepath = writer.write_subsidence(gw)

        reader = GroundwaterReader()
        result = reader.read_subsidence(filepath)

        assert len(result) == 1
        assert result[0].element == 3
        assert result[0].layer == 1
        assert result[0].elastic_storage == pytest.approx(1.5e-5, rel=1e-4)
        assert result[0].inelastic_storage == pytest.approx(2.0e-4, rel=1e-4)
        assert result[0].preconsolidation_head == pytest.approx(95.0, rel=1e-3)

    def test_roundtrip_multiple(self, tmp_path: Path) -> None:
        """Test write/read roundtrip with multiple subsidence entries."""
        gw = AppGW(n_nodes=20, n_layers=3, n_elements=10)
        subsidence_data = [
            Subsidence(
                element=1,
                layer=1,
                elastic_storage=1e-5,
                inelastic_storage=1e-4,
                preconsolidation_head=90.0,
            ),
            Subsidence(
                element=1,
                layer=2,
                elastic_storage=2e-5,
                inelastic_storage=2e-4,
                preconsolidation_head=85.0,
            ),
            Subsidence(
                element=5,
                layer=1,
                elastic_storage=3e-6,
                inelastic_storage=5e-5,
                preconsolidation_head=100.0,
            ),
            Subsidence(
                element=8,
                layer=3,
                elastic_storage=8e-6,
                inelastic_storage=9e-4,
                preconsolidation_head=70.0,
            ),
        ]
        for sub in subsidence_data:
            gw.add_subsidence(sub)

        config = GWFileConfig(output_dir=tmp_path)
        writer = GroundwaterWriter(config)
        filepath = writer.write_subsidence(gw)

        reader = GroundwaterReader()
        result = reader.read_subsidence(filepath)

        assert len(result) == 4
        for orig, read in zip(subsidence_data, result, strict=False):
            assert read.element == orig.element
            assert read.layer == orig.layer
            assert read.elastic_storage == pytest.approx(orig.elastic_storage, rel=1e-3)
            assert read.inelastic_storage == pytest.approx(orig.inelastic_storage, rel=1e-3)
            assert read.preconsolidation_head == pytest.approx(orig.preconsolidation_head, rel=1e-3)

    def test_roundtrip_extreme_values(self, tmp_path: Path) -> None:
        """Test roundtrip with extreme but valid values."""
        gw = AppGW(n_nodes=10, n_layers=2, n_elements=5)
        gw.add_subsidence(
            Subsidence(
                element=1,
                layer=1,
                elastic_storage=1e-9,
                inelastic_storage=1e-1,
                preconsolidation_head=0.0,
            )
        )
        gw.add_subsidence(
            Subsidence(
                element=2,
                layer=2,
                elastic_storage=9.99e-3,
                inelastic_storage=9.99e-2,
                preconsolidation_head=999.9999,
            )
        )

        config = GWFileConfig(output_dir=tmp_path)
        writer = GroundwaterWriter(config)
        filepath = writer.write_subsidence(gw)

        reader = GroundwaterReader()
        result = reader.read_subsidence(filepath)

        assert len(result) == 2
        assert result[0].preconsolidation_head == pytest.approx(0.0)
        assert result[1].preconsolidation_head == pytest.approx(999.9999, rel=1e-3)

    def test_roundtrip_precision(self, tmp_path: Path) -> None:
        """Test precision of roundtrip is within expected tolerance."""
        gw = AppGW(n_nodes=10, n_layers=2, n_elements=5)
        original = Subsidence(
            element=1,
            layer=1,
            elastic_storage=1.234567e-5,
            inelastic_storage=9.876543e-4,
            preconsolidation_head=123.4567,
        )
        gw.add_subsidence(original)

        config = GWFileConfig(output_dir=tmp_path)
        writer = GroundwaterWriter(config)
        filepath = writer.write_subsidence(gw)

        reader = GroundwaterReader()
        result = reader.read_subsidence(filepath)

        assert len(result) == 1
        # Scientific notation format: 12.6e gives 6 significant digits
        assert result[0].elastic_storage == pytest.approx(original.elastic_storage, rel=1e-5)
        assert result[0].inelastic_storage == pytest.approx(original.inelastic_storage, rel=1e-5)
        # Fixed format: 12.4f gives 4 decimal places
        assert result[0].preconsolidation_head == pytest.approx(
            original.preconsolidation_head, rel=1e-3
        )


# =============================================================================
# Test Convenience Function
# =============================================================================


class TestConvenienceReadSubsidence:
    """Tests for module-level read_subsidence function."""

    def test_convenience_function(self, tmp_path: Path) -> None:
        """Test read_subsidence module-level convenience function."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  Subsidence file
2                              / N_SUBSIDENCE
    1     1   1.000000e-05   1.000000e-04      90.0000
    2     1   2.000000e-05   2.000000e-04      85.0000
""")

        result = read_subsidence(filepath)

        assert len(result) == 2
        assert result[0].element == 1
        assert result[1].element == 2

    def test_convenience_function_string_path(self, tmp_path: Path) -> None:
        """Test read_subsidence with string path argument."""
        filepath = tmp_path / "subsidence.dat"
        filepath.write_text("""C  Subsidence file
1                              / N_SUBSIDENCE
    1     1   1.000000e-05   1.000000e-04      90.0000
""")

        result = read_subsidence(str(filepath))

        assert len(result) == 1


# =============================================================================
# Test Write with Custom Header
# =============================================================================


class TestWriteSubsidenceCustomHeader:
    """Tests for GroundwaterWriter.write_subsidence with custom header."""

    def test_write_custom_header(self, tmp_path: Path) -> None:
        """Test writing subsidence with custom header."""
        gw = AppGW(n_nodes=10, n_layers=2, n_elements=5)
        gw.add_subsidence(
            Subsidence(
                element=1,
                layer=1,
                elastic_storage=1e-5,
                inelastic_storage=1e-4,
                preconsolidation_head=90.0,
            )
        )

        config = GWFileConfig(output_dir=tmp_path)
        writer = GroundwaterWriter(config)
        filepath = writer.write_subsidence(gw, header="Custom subsidence header\nVersion 2.0")

        content = filepath.read_text()
        assert "Custom subsidence header" in content
        assert "Version 2.0" in content

    def test_write_default_header(self, tmp_path: Path) -> None:
        """Test that default header includes expected keywords."""
        gw = AppGW(n_nodes=10, n_layers=2, n_elements=5)
        gw.add_subsidence(
            Subsidence(
                element=1,
                layer=1,
                elastic_storage=1e-5,
                inelastic_storage=1e-4,
                preconsolidation_head=90.0,
            )
        )

        config = GWFileConfig(output_dir=tmp_path)
        writer = GroundwaterWriter(config)
        filepath = writer.write_subsidence(gw)

        content = filepath.read_text()
        assert "Subsidence parameters file" in content
        assert "Generated by pyiwfm" in content
        assert "ELEM" in content
        assert "LAYER" in content
        assert "ELASTIC_S" in content
