"""
Tests for NormalizationReport model and NormalizationConfig parsing.
Covers NORM-2 acceptance criteria.
"""

import pytest
from atomingest.normalization.core import (
    NormalizationReport,
    NormalizationConfig,
    NormalizationStepConfig,
)


class TestNormalizationReport:
    """Test NormalizationReport counters and warning helpers."""

    def test_initialization_defaults(self):
        """Report should initialize with zero counters and empty collections."""
        report = NormalizationReport()

        assert report.total_lines_read == 0
        assert report.metadata_lines_skipped == 0
        assert report.trailer_lines_skipped == 0
        assert report.duplicate_headers_removed == 0
        assert report.rows_emitted == 0
        assert report.row_count_trailer_value is None
        assert report.warnings == []
        assert report.extracted_metadata == {}

    def test_add_warning(self):
        """add_warning() should append messages to warnings list."""
        report = NormalizationReport()

        report.add_warning("BOM detected and removed")
        report.add_warning("Missing expected column: customer_id")

        assert len(report.warnings) == 2
        assert report.warnings[0] == "BOM detected and removed"
        assert report.warnings[1] == "Missing expected column: customer_id"

    def test_increment_counters(self):
        """Increment methods should update respective counters."""
        report = NormalizationReport()

        # Simulate processing
        report.increment_total_lines()
        report.increment_total_lines()
        report.increment_metadata_skipped()
        report.increment_trailer_skipped()
        report.increment_duplicate_headers()
        report.increment_rows_emitted()

        assert report.total_lines_read == 2
        assert report.metadata_lines_skipped == 1
        assert report.trailer_lines_skipped == 1
        assert report.duplicate_headers_removed == 1
        assert report.rows_emitted == 1

    def test_set_trailer_row_count(self):
        """set_trailer_row_count() should store extracted value."""
        report = NormalizationReport()

        report.set_trailer_row_count(1234)

        assert report.row_count_trailer_value == 1234

    def test_extracted_metadata_storage(self):
        """Metadata dictionary should be mutable and store arbitrary values."""
        report = NormalizationReport()

        report.extracted_metadata["report_date"] = "2024-01-15"
        report.extracted_metadata["source_system"] = "legacy_mainframe"

        assert report.extracted_metadata["report_date"] == "2024-01-15"
        assert report.extracted_metadata["source_system"] == "legacy_mainframe"


class TestNormalizationConfig:
    """Test NormalizationConfig and NormalizationStepConfig parsing."""

    def test_step_config_basic(self):
        """NormalizationStepConfig should store step name and config."""
        step = NormalizationStepConfig(
            step="RemoveBOMStep", config={"all_lines": False}, enabled=True
        )

        assert step.step == "RemoveBOMStep"
        assert step.config == {"all_lines": False}
        assert step.enabled is True

    def test_normalization_config_defaults(self):
        """NormalizationConfig should have sensible defaults."""
        config = NormalizationConfig()

        assert config.strict is False
        assert config.steps == []

    def test_from_dict_minimal(self):
        """from_dict() should parse minimal valid configuration."""
        data = {"strict": False, "steps": []}

        config = NormalizationConfig.from_dict(data)

        assert config.strict is False
        assert config.steps == []

    def test_from_dict_with_steps(self):
        """from_dict() should parse steps list with configs."""
        data = {
            "strict": True,
            "steps": [
                {
                    "step": "RemoveBOMStep",
                    "config": {"all_lines": False},
                    "enabled": True,
                },
                {
                    "step": "SkipUntilHeaderStep",
                    "config": {
                        "expected_columns": ["id", "name", "email"],
                        "max_skip": 10,
                    },
                },
                {"step": "TrimWhitespaceStep", "enabled": False},
            ],
        }

        config = NormalizationConfig.from_dict(data)

        assert config.strict is True
        assert len(config.steps) == 3

        # Verify first step
        assert config.steps[0].step == "RemoveBOMStep"
        assert config.steps[0].config == {"all_lines": False}
        assert config.steps[0].enabled is True

        # Verify second step
        assert config.steps[1].step == "SkipUntilHeaderStep"
        assert config.steps[1].config["expected_columns"] == ["id", "name", "email"]
        assert config.steps[1].config["max_skip"] == 10
        assert config.steps[1].enabled is True  # Default

        # Verify third step (disabled)
        assert config.steps[2].step == "TrimWhitespaceStep"
        assert config.steps[2].config == {}  # Default
        assert config.steps[2].enabled is False

    def test_from_dict_missing_step_name_raises_error(self):
        """from_dict() should raise ValueError if step name is missing."""
        data = {
            "steps": [
                {"config": {"some_option": True}}  # Missing 'step' field
            ]
        }

        with pytest.raises(ValueError, match="missing required 'step' name"):
            NormalizationConfig.from_dict(data)

    def test_from_dict_invalid_step_format_raises_error(self):
        """from_dict() should raise ValueError for non-dict step definitions."""
        data = {
            "steps": ["RemoveBOMStep"]  # String instead of dict
        }

        with pytest.raises(ValueError, match="Invalid step definition"):
            NormalizationConfig.from_dict(data)

    def test_from_dict_empty_config_uses_defaults(self):
        """from_dict() should handle missing 'strict' and 'steps' keys."""
        data = {}

        config = NormalizationConfig.from_dict(data)

        assert config.strict is False
        assert config.steps == []

    def test_from_dict_step_without_config(self):
        """from_dict() should allow steps without explicit config dict."""
        data = {"steps": [{"step": "DropRepeatedHeaderRowsStep"}]}

        config = NormalizationConfig.from_dict(data)

        assert len(config.steps) == 1
        assert config.steps[0].step == "DropRepeatedHeaderRowsStep"
        assert config.steps[0].config == {}
        assert config.steps[0].enabled is True
