"""
Tests for the integration hook module.
"""

import io
import tempfile
from pathlib import Path

from atomingest.normalization.integration import (
    normalize_then_validate,
    normalize_csv_file,
    normalize_csv_string,
)
from atomingest.normalization.core import (
    NormalizationConfig,
    NormalizationStepConfig,
)


class TestNormalizeThenValidate:
    """Test the main normalize_then_validate function."""

    def test_basic_normalization_with_string_io(self):
        """Test basic normalization with StringIO source."""
        csv_data = """id,name,email
1,Alice,alice@example.com
2,Bob,bob@example.com"""

        rows, report = normalize_then_validate(io.StringIO(csv_data))

        assert len(rows) == 2
        assert rows[0]["id"] == "1"
        assert rows[0]["name"] == "Alice"
        assert rows[1]["name"] == "Bob"
        assert report.rows_emitted == 2
        assert len(report.warnings) == 0

    def test_normalization_with_steps(self):
        """Test normalization with specific steps configured."""
        csv_data = """id,name,email
1,  Alice  ,alice@example.com
2,  Bob  ,bob@example.com"""

        steps = [
            NormalizationStepConfig(step="TrimWhitespaceStep", config={}, enabled=True)
        ]

        rows, report = normalize_then_validate(io.StringIO(csv_data), steps=steps)

        assert len(rows) == 2
        assert rows[0]["name"] == "Alice"  # Whitespace trimmed
        assert rows[1]["name"] == "Bob"
        assert report.rows_emitted == 2

    def test_normalization_with_config_object(self):
        """Test normalization with a NormalizationConfig object."""
        csv_data = """id,name
1,Alice
2,Bob"""

        config = NormalizationConfig(
            strict=False,
            steps=[
                NormalizationStepConfig(
                    step="TrimWhitespaceStep", config={}, enabled=True
                )
            ],
        )

        rows, report = normalize_then_validate(io.StringIO(csv_data), config=config)

        assert len(rows) == 2
        assert report.rows_emitted == 2

    def test_normalization_strict_mode(self):
        """Test normalization in strict mode."""
        csv_data = """id,name
1,Alice
2,Bob"""

        rows, report = normalize_then_validate(
            io.StringIO(csv_data), strict=True, steps=[]
        )

        assert len(rows) == 2
        assert report.rows_emitted == 2

    def test_normalization_with_null_tokens(self):
        """Test normalization with null token handling."""
        csv_data = """id,name,email
1,Alice,alice@example.com
2,Bob,NA
3,Charlie,"""

        steps = [
            NormalizationStepConfig(
                step="NullTokenNormalizationStep", config={}, enabled=True
            )
        ]

        rows, report = normalize_then_validate(io.StringIO(csv_data), steps=steps)

        assert len(rows) == 3
        assert rows[1]["email"] is None  # "NA" converted to None
        assert rows[2]["email"] is None  # Empty string converted to None

    def test_normalization_with_bom(self):
        """Test normalization with UTF-8 BOM."""
        csv_data = "\ufeffid,name\n1,Alice\n2,Bob"

        steps = [NormalizationStepConfig(step="RemoveBOMStep", config={}, enabled=True)]

        rows, report = normalize_then_validate(io.StringIO(csv_data), steps=steps)

        assert len(rows) == 2
        assert "UTF-8 BOM detected" in report.warnings[0]
        assert report.rows_emitted == 2

    def test_normalization_with_multiple_steps(self):
        """Test normalization with multiple steps in sequence."""
        csv_data = """\ufeffid,name,active
1,  Alice  ,yes
2,  Bob  ,no"""

        steps = [
            NormalizationStepConfig(step="RemoveBOMStep", config={}, enabled=True),
            NormalizationStepConfig(step="TrimWhitespaceStep", config={}, enabled=True),
        ]

        rows, report = normalize_then_validate(io.StringIO(csv_data), steps=steps)

        assert len(rows) == 2
        assert rows[0]["name"] == "Alice"
        assert rows[1]["name"] == "Bob"
        assert len(report.warnings) > 0  # BOM warning

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

        rows, report = normalize_then_validate(io.StringIO(csv_data))

        assert len(rows) == 0
        assert report.rows_emitted == 0

    def test_normalization_no_steps(self):
        """Test normalization without any steps."""
        csv_data = """id,name
1,Alice
2,Bob"""

        rows, report = normalize_then_validate(io.StringIO(csv_data), steps=[])

        assert len(rows) == 2
        assert report.rows_emitted == 2

    def test_normalization_with_disabled_step(self):
        """Test that disabled steps are not executed."""
        csv_data = """id,name
1,"  Alice  "
2,"  Bob  " """

        steps = [
            NormalizationStepConfig(step="TrimWhitespaceStep", config={}, enabled=False)
        ]

        rows, report = normalize_then_validate(io.StringIO(csv_data), steps=steps)

        # Whitespace should be preserved in quoted CSV fields
        assert rows[0]["name"] == "  Alice  "
        assert "Bob" in rows[1]["name"]  # Check Bob is there with whitespace

    def test_return_types(self):
        """Test that function returns correct types."""
        csv_data = """id,name
1,Alice"""

        rows, report = normalize_then_validate(io.StringIO(csv_data))

        assert isinstance(rows, list)
        assert isinstance(rows[0], dict)
        assert hasattr(report, "rows_emitted")
        assert hasattr(report, "warnings")
        assert hasattr(report, "row_count_trailer_value")


class TestNormalizeCsvFile:
    """Test the normalize_csv_file convenience function."""

    def test_normalize_file_with_path_string(self):
        """Test normalizing a file using a path string."""
        csv_data = """id,name,email
1,Alice,alice@example.com
2,Bob,bob@example.com"""

        # Create a temporary file
        with tempfile.NamedTemporaryFile(
            mode="w", delete=False, suffix=".csv"
        ) as temp_file:
            temp_file.write(csv_data)
            temp_path = temp_file.name

        try:
            rows, report = normalize_csv_file(temp_path)

            assert len(rows) == 2
            assert rows[0]["name"] == "Alice"
            assert rows[1]["name"] == "Bob"
            assert report.rows_emitted == 2
        finally:
            # Clean up
            Path(temp_path).unlink()

    def test_normalize_file_with_path_object(self):
        """Test normalizing a file using a Path object."""
        csv_data = """id,name
1,Alice
2,Bob"""

        with tempfile.NamedTemporaryFile(
            mode="w", delete=False, suffix=".csv"
        ) as temp_file:
            temp_file.write(csv_data)
            temp_path = Path(temp_file.name)

        try:
            rows, report = normalize_csv_file(temp_path)

            assert len(rows) == 2
            assert report.rows_emitted == 2
        finally:
            temp_path.unlink()

    def test_normalize_file_with_steps(self):
        """Test normalizing a file with specific steps."""
        csv_data = """id,name
1,  Alice
2,  Bob  """

        with tempfile.NamedTemporaryFile(
            mode="w", delete=False, suffix=".csv"
        ) as temp_file:
            temp_file.write(csv_data)
            temp_path = temp_file.name

        try:
            steps = [
                NormalizationStepConfig(
                    step="TrimWhitespaceStep", config={}, enabled=True
                )
            ]
            rows, report = normalize_csv_file(temp_path, steps=steps)

            assert rows[0]["name"] == "Alice"  # Trimmed
            assert rows[1]["name"] == "Bob"
        finally:
            Path(temp_path).unlink()

    def test_normalize_file_strict_mode(self):
        """Test normalizing a file in strict mode."""
        csv_data = """id,name
1,Alice
2,Bob"""

        with tempfile.NamedTemporaryFile(
            mode="w", delete=False, suffix=".csv"
        ) as temp_file:
            temp_file.write(csv_data)
            temp_path = temp_file.name

        try:
            rows, report = normalize_csv_file(temp_path, strict=True)

            assert len(rows) == 2
            assert report.rows_emitted == 2
        finally:
            Path(temp_path).unlink()


class TestNormalizeCsvString:
    """Test the normalize_csv_string convenience function."""

    def test_normalize_string_basic(self):
        """Test normalizing CSV content from a string."""
        csv_data = """id,name,email
1,Alice,alice@example.com
2,Bob,bob@example.com"""

        rows, report = normalize_csv_string(csv_data)

        assert len(rows) == 2
        assert rows[0]["name"] == "Alice"
        assert rows[1]["name"] == "Bob"
        assert report.rows_emitted == 2

    def test_normalize_string_with_steps(self):
        """Test normalizing string with specific steps."""
        csv_data = """id,name
1,  Alice
2,  Bob  """

        steps = [
            NormalizationStepConfig(step="TrimWhitespaceStep", config={}, enabled=True)
        ]

        rows, report = normalize_csv_string(csv_data, steps=steps)

        assert rows[0]["name"] == "Alice"
        assert rows[1]["name"] == "Bob"

    def test_normalize_string_with_newlines(self):
        """Test normalizing string with various newline formats."""
        csv_data = "id,name\n1,Alice\n2,Bob"

        rows, report = normalize_csv_string(csv_data)

        assert len(rows) == 2
        assert rows[0]["id"] == "1"
        assert rows[1]["id"] == "2"

    def test_normalize_string_empty(self):
        """Test normalizing empty CSV string."""
        csv_data = "id,name"

        rows, report = normalize_csv_string(csv_data)

        assert len(rows) == 0
        assert report.rows_emitted == 0

    def test_normalize_string_strict_mode(self):
        """Test normalizing string in strict mode."""
        csv_data = """id,name
1,Alice
2,Bob"""

        rows, report = normalize_csv_string(csv_data, strict=True)

        assert len(rows) == 2
        assert report.rows_emitted == 2


class TestIntegrationUseCases:
    """Test real-world integration use cases."""

    def test_full_normalization_pipeline(self):
        """Test a complete normalization pipeline with multiple steps."""
        csv_data = """\ufeffid,name,email,active
1,  Alice Smith  ,alice@example.com,yes
2,Bob Jones,NA,no
3,  Charlie  ,,1"""

        steps = [
            NormalizationStepConfig(step="RemoveBOMStep", config={}, enabled=True),
            NormalizationStepConfig(step="TrimWhitespaceStep", config={}, enabled=True),
            NormalizationStepConfig(
                step="NullTokenNormalizationStep", config={}, enabled=True
            ),
        ]

        rows, report = normalize_then_validate(io.StringIO(csv_data), steps=steps)

        assert len(rows) == 3
        assert rows[0]["name"] == "Alice Smith"
        assert rows[1]["email"] is None  # NA -> None
        assert rows[2]["email"] is None  # Empty -> None
        assert len(report.warnings) > 0  # BOM warning

    def test_can_access_report_metrics(self):
        """Test that report metrics are accessible after normalization."""
        csv_data = """id,name
1,Alice
2,Bob
3,Charlie"""

        rows, report = normalize_then_validate(io.StringIO(csv_data))

        # Check all report attributes are accessible
        assert report.total_lines_read > 0
        assert report.rows_emitted == 3
        assert report.metadata_lines_skipped >= 0
        assert report.trailer_lines_skipped >= 0
        assert report.duplicate_headers_removed >= 0
        assert isinstance(report.warnings, list)
        assert isinstance(report.extracted_metadata, dict)

    def test_integration_without_coupling_to_database(self):
        """Test that integration works without any database dependency."""
        csv_data = """id,name
1,Alice
2,Bob"""

        # This should work without any database imports or connections
        rows, report = normalize_then_validate(io.StringIO(csv_data))

        # Just return data and report - no DB operations
        assert len(rows) == 2
        assert isinstance(rows, list)
        assert isinstance(report.warnings, list)

    def test_can_be_called_by_other_modules(self):
        """Test that the function can be easily called by external modules."""

        # Simulate external module usage
        def external_module_function(data):
            """Simulated external module that needs normalization."""
            return normalize_csv_string(data)

        csv_data = "id,name\n1,Alice\n2,Bob"
        rows, report = external_module_function(csv_data)

        assert len(rows) == 2
        assert report.rows_emitted == 2
