"""
Tests for trailer row count validation functionality.
"""

import pytest
import io
from atomingest.normalization.core import NormalizationReport, NormalizationConfig
from atomingest.normalization.pipeline import NormalizationPipeline


class TestNormalizationReportValidation:
    """Test the validate_row_count method in NormalizationReport."""

    def test_validate_row_count_match(self):
        """Test validation passes when counts match."""
        report = NormalizationReport()
        report.rows_emitted = 5
        report.set_trailer_row_count(5)

        # Should not raise or add warnings
        report.validate_row_count(strict=False)
        assert len(report.warnings) == 0

    def test_validate_row_count_match_strict(self):
        """Test validation passes in strict mode when counts match."""
        report = NormalizationReport()
        report.rows_emitted = 10
        report.set_trailer_row_count(10)

        # Should not raise
        report.validate_row_count(strict=True)
        assert len(report.warnings) == 0

    def test_validate_row_count_mismatch_non_strict(self):
        """Test validation adds warning when counts don't match in non-strict mode."""
        report = NormalizationReport()
        report.rows_emitted = 5
        report.set_trailer_row_count(10)

        # Should add warning but not raise
        report.validate_row_count(strict=False)
        assert len(report.warnings) == 1
        assert "Row count mismatch" in report.warnings[0]
        assert "trailer indicates 10 rows" in report.warnings[0]
        assert "5 rows were emitted" in report.warnings[0]

    def test_validate_row_count_mismatch_strict(self):
        """Test validation raises error when counts don't match in strict mode."""
        report = NormalizationReport()
        report.rows_emitted = 5
        report.set_trailer_row_count(10)

        # Should raise ValueError
        with pytest.raises(ValueError, match="Row count mismatch"):
            report.validate_row_count(strict=True)

    def test_validate_row_count_no_trailer(self):
        """Test validation does nothing when no trailer count is set."""
        report = NormalizationReport()
        report.rows_emitted = 5
        # Don't set trailer count

        # Should not raise or add warnings
        report.validate_row_count(strict=False)
        assert len(report.warnings) == 0

        # Should not raise in strict mode either
        report.validate_row_count(strict=True)
        assert len(report.warnings) == 0

    def test_validate_row_count_zero_rows(self):
        """Test validation with zero rows emitted."""
        report = NormalizationReport()
        report.rows_emitted = 0
        report.set_trailer_row_count(0)

        # Should pass
        report.validate_row_count(strict=False)
        assert len(report.warnings) == 0

    def test_validate_row_count_zero_mismatch(self):
        """Test validation when trailer says 0 but rows were emitted."""
        report = NormalizationReport()
        report.rows_emitted = 5
        report.set_trailer_row_count(0)

        # Should add warning
        report.validate_row_count(strict=False)
        assert len(report.warnings) == 1
        assert "0 rows" in report.warnings[0]
        assert "5 rows were emitted" in report.warnings[0]

    def test_validate_row_count_more_than_trailer(self):
        """Test validation when more rows emitted than trailer indicates."""
        report = NormalizationReport()
        report.rows_emitted = 15
        report.set_trailer_row_count(10)

        # Should add warning
        report.validate_row_count(strict=False)
        assert len(report.warnings) == 1
        assert "trailer indicates 10 rows" in report.warnings[0]
        assert "15 rows were emitted" in report.warnings[0]

    def test_validate_row_count_less_than_trailer(self):
        """Test validation when fewer rows emitted than trailer indicates."""
        report = NormalizationReport()
        report.rows_emitted = 8
        report.set_trailer_row_count(10)

        # Should add warning
        report.validate_row_count(strict=False)
        assert len(report.warnings) == 1
        assert "trailer indicates 10 rows" in report.warnings[0]
        assert "8 rows were emitted" in report.warnings[0]


class TestPipelineValidation:
    """Test trailer row count validation in the pipeline."""

    def test_pipeline_validates_after_processing(self):
        """Test that pipeline validates row count after all rows are processed."""
        csv_data = """id,name
1,Alice
2,Bob
3,Charlie"""

        config = NormalizationConfig(strict=False, steps=[])
        pipeline = NormalizationPipeline(config=config)

        rows, report = pipeline.run(io.StringIO(csv_data))
        # Set trailer count manually (simulating DropTrailerLinesStep behavior)
        report.set_trailer_row_count(5)  # Wrong count

        # Consume all rows to trigger validation
        result = list(rows)

        assert len(result) == 3
        assert report.rows_emitted == 3
        assert report.row_count_trailer_value == 5
        # Validation should have added a warning
        assert len(report.warnings) > 0
        assert any("Row count mismatch" in w for w in report.warnings)

    def test_pipeline_validation_matching_count(self):
        """Test pipeline with matching row count doesn't add warning."""
        csv_data = """id,name
1,Alice
2,Bob
3,Charlie"""

        config = NormalizationConfig(strict=False, steps=[])
        pipeline = NormalizationPipeline(config=config)

        rows, report = pipeline.run(io.StringIO(csv_data))
        # Set correct trailer count
        report.set_trailer_row_count(3)

        # Consume all rows to trigger validation
        result = list(rows)

        assert len(result) == 3
        assert report.rows_emitted == 3
        assert report.row_count_trailer_value == 3
        # No warnings should be added
        assert len(report.warnings) == 0

    def test_pipeline_validation_strict_mode_raises(self):
        """Test pipeline in strict mode raises on row count mismatch."""
        csv_data = """id,name
1,Alice
2,Bob"""

        config = NormalizationConfig(strict=True, steps=[])
        pipeline = NormalizationPipeline(config=config)

        rows, report = pipeline.run(io.StringIO(csv_data))
        # Set wrong trailer count
        report.set_trailer_row_count(5)

        # Consuming rows should raise ValueError due to mismatch
        with pytest.raises(ValueError, match="Row count mismatch"):
            list(rows)

    def test_pipeline_no_trailer_no_validation(self):
        """Test pipeline without trailer count doesn't validate."""
        csv_data = """id,name
1,Alice
2,Bob"""

        config = NormalizationConfig(strict=False, steps=[])
        pipeline = NormalizationPipeline(config=config)

        rows, report = pipeline.run(io.StringIO(csv_data))
        # Don't set trailer count

        # Consume all rows
        result = list(rows)

        assert len(result) == 2
        assert report.rows_emitted == 2
        assert report.row_count_trailer_value is None
        # No warnings
        assert len(report.warnings) == 0

    def test_pipeline_validation_empty_file(self):
        """Test validation with empty CSV file."""
        csv_data = """id,name"""

        config = NormalizationConfig(strict=False, steps=[])
        pipeline = NormalizationPipeline(config=config)

        rows, report = pipeline.run(io.StringIO(csv_data))
        report.set_trailer_row_count(0)

        # Consume all rows
        result = list(rows)

        assert len(result) == 0
        assert report.rows_emitted == 0
        assert report.row_count_trailer_value == 0
        # Should match, no warnings
        assert len(report.warnings) == 0

    def test_pipeline_validation_with_multiple_warnings(self):
        """Test that validation warning is added alongside other warnings."""
        csv_data = """id,name
1,Alice
2,Bob"""

        config = NormalizationConfig(strict=False, steps=[])
        pipeline = NormalizationPipeline(config=config)

        rows, report = pipeline.run(io.StringIO(csv_data))
        # Add some other warnings
        report.add_warning("Some other warning")
        report.set_trailer_row_count(5)

        # Consume all rows
        result = list(rows)

        assert len(result) == 2
        assert len(report.warnings) == 2
        assert "Some other warning" in report.warnings[0]
        assert "Row count mismatch" in report.warnings[1]

    def test_report_includes_both_counts(self):
        """Test that report includes both trailer and emitted counts."""
        csv_data = """id,name
1,Alice
2,Bob
3,Charlie
4,Diana"""

        config = NormalizationConfig(strict=False, steps=[])
        pipeline = NormalizationPipeline(config=config)

        rows, report = pipeline.run(io.StringIO(csv_data))
        report.set_trailer_row_count(3)

        # Consume all rows
        result = list(rows)

        # Both counts should be available in the report
        assert report.row_count_trailer_value == 3
        assert report.rows_emitted == 4
        assert len(result) == 4

        # Warning should mention both counts
        assert len(report.warnings) == 1
        assert "3 rows" in report.warnings[0]
        assert "4 rows" in report.warnings[0]
