"""
Tests for BasicTypeCleanupStep normalization step.
"""

import pytest
from atomingest.normalization.standard_steps import BasicTypeCleanupStep
from atomingest.normalization.core import NormalizationReport


class TestBasicTypeCleanupStepNumeric:
    """Test numeric cleanup functionality."""

    def test_remove_commas_from_numeric_strings(self):
        """Test removing commas from numeric strings."""
        report = NormalizationReport()
        config = {"numeric_columns": ["salary", "revenue"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"name": "Alice", "salary": "85,000", "revenue": "1,234,567.89"}
        result = step.process_row(row)

        assert result["salary"] == "85000"
        assert result["revenue"] == "1234567.89"
        assert result["name"] == "Alice"

    def test_convert_numeric_strings_to_numbers(self):
        """Test converting numeric strings to actual numbers."""
        report = NormalizationReport()
        config = {"numeric_columns": ["salary", "count"], "convert_to_number": True}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"salary": "85,000.50", "count": "1,234"}
        result = step.process_row(row)

        assert result["salary"] == 85000.50
        assert isinstance(result["salary"], float)
        assert result["count"] == 1234
        assert isinstance(result["count"], int)

    def test_numeric_column_with_none_value(self):
        """Test that None values are preserved in numeric columns."""
        report = NormalizationReport()
        config = {"numeric_columns": ["salary"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"name": "Bob", "salary": None}
        result = step.process_row(row)

        assert result["salary"] is None

    def test_numeric_column_invalid_value_non_strict(self):
        """Test invalid numeric value in non-strict mode with convert_to_number."""
        report = NormalizationReport()
        config = {"numeric_columns": ["salary"], "convert_to_number": True}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"salary": "not-a-number"}
        result = step.process_row(row)

        # Should preserve original value and add warning
        assert result["salary"] == "not-a-number"
        assert len(report.warnings) > 0
        assert "Failed to parse numeric" in report.warnings[0]

    def test_numeric_column_invalid_value_strict_mode(self):
        """Test invalid numeric value in strict mode raises error."""
        report = NormalizationReport()
        config = {"numeric_columns": ["salary"], "convert_to_number": True}
        step = BasicTypeCleanupStep(config=config, report=report, strict=True)

        row = {"salary": "invalid"}
        with pytest.raises(ValueError, match="Failed to parse numeric"):
            step.process_row(row)

    def test_numeric_cleanup_empty_list(self):
        """Test that no columns are processed when numeric_columns is empty."""
        report = NormalizationReport()
        config = {"numeric_columns": []}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"salary": "85,000", "count": "1,234"}
        result = step.process_row(row)

        # Values should remain unchanged
        assert result["salary"] == "85,000"
        assert result["count"] == "1,234"


class TestBasicTypeCleanupStepBoolean:
    """Test boolean normalization functionality."""

    def test_boolean_normalization_true_values(self):
        """Test normalizing various true values."""
        report = NormalizationReport()
        config = {"boolean_columns": ["active", "enabled", "verified"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"active": "true", "enabled": "yes", "verified": "1"}
        result = step.process_row(row)

        assert result["active"] is True
        assert result["enabled"] is True
        assert result["verified"] is True

    def test_boolean_normalization_false_values(self):
        """Test normalizing various false values."""
        report = NormalizationReport()
        config = {"boolean_columns": ["active", "enabled", "verified"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"active": "false", "enabled": "no", "verified": "0"}
        result = step.process_row(row)

        assert result["active"] is False
        assert result["enabled"] is False
        assert result["verified"] is False

    def test_boolean_normalization_case_insensitive(self):
        """Test that boolean normalization is case-insensitive."""
        report = NormalizationReport()
        config = {"boolean_columns": ["flag"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        for value in ["TRUE", "True", "TrUe", "YES", "Yes"]:
            row = {"flag": value}
            result = step.process_row(row)
            assert result["flag"] is True

        for value in ["FALSE", "False", "FaLsE", "NO", "No"]:
            row = {"flag": value}
            result = step.process_row(row)
            assert result["flag"] is False

    def test_boolean_normalization_with_whitespace(self):
        """Test that boolean values are trimmed before normalization."""
        report = NormalizationReport()
        config = {"boolean_columns": ["active"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"active": "  true  "}
        result = step.process_row(row)

        assert result["active"] is True

    def test_boolean_custom_values(self):
        """Test custom boolean true/false values."""
        report = NormalizationReport()
        config = {
            "boolean_columns": ["status"],
            "true_values": ["active", "enabled"],
            "false_values": ["inactive", "disabled"],
        }
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row1 = {"status": "active"}
        result1 = step.process_row(row1)
        assert result1["status"] is True

        row2 = {"status": "inactive"}
        result2 = step.process_row(row2)
        assert result2["status"] is False

    def test_boolean_invalid_value_non_strict(self):
        """Test invalid boolean value in non-strict mode."""
        report = NormalizationReport()
        config = {"boolean_columns": ["active"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"active": "maybe"}
        result = step.process_row(row)

        # Should preserve original value and add warning
        assert result["active"] == "maybe"
        assert len(report.warnings) > 0
        assert "Failed to parse boolean" in report.warnings[0]

    def test_boolean_invalid_value_strict_mode(self):
        """Test invalid boolean value in strict mode raises error."""
        report = NormalizationReport()
        config = {"boolean_columns": ["active"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=True)

        row = {"active": "invalid"}
        with pytest.raises(ValueError, match="Failed to parse boolean"):
            step.process_row(row)

    def test_boolean_column_with_none_value(self):
        """Test that None values are preserved in boolean columns."""
        report = NormalizationReport()
        config = {"boolean_columns": ["active"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"active": None}
        result = step.process_row(row)

        assert result["active"] is None

    def test_boolean_column_with_actual_boolean(self):
        """Test that actual boolean values are preserved."""
        report = NormalizationReport()
        config = {"boolean_columns": ["active"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"active": True}
        result = step.process_row(row)
        assert result["active"] is True

        row2 = {"active": False}
        result2 = step.process_row(row2)
        assert result2["active"] is False


class TestBasicTypeCleanupStepDate:
    """Test date parsing functionality."""

    def test_date_parsing_iso_format(self):
        """Test parsing dates in ISO format."""
        report = NormalizationReport()
        config = {"date_columns": ["created_date", "updated_date"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"created_date": "2024-01-15", "updated_date": "2024-02-20"}
        result = step.process_row(row)

        assert result["created_date"] == "2024-01-15"
        assert result["updated_date"] == "2024-02-20"

    def test_date_parsing_multiple_formats(self):
        """Test parsing dates in various formats."""
        report = NormalizationReport()
        config = {"date_columns": ["date"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        # Test US format (MM/DD/YYYY)
        row1 = {"date": "01/15/2024"}
        result1 = step.process_row(row1)
        assert result1["date"] == "2024-01-15"

        # Test European format (DD/MM/YYYY)
        report2 = NormalizationReport()
        config2 = {
            "date_columns": ["date"],
            "date_formats": ["%d/%m/%Y"],  # Explicitly set European format
        }
        step2 = BasicTypeCleanupStep(config=config2, report=report2, strict=False)
        row2 = {"date": "15/01/2024"}
        result2 = step2.process_row(row2)
        assert result2["date"] == "2024-01-15"

    def test_date_parsing_with_time(self):
        """Test parsing dates with time components."""
        report = NormalizationReport()
        config = {"date_columns": ["timestamp"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"timestamp": "2024-01-15 14:30:00"}
        result = step.process_row(row)

        # Default output format is date only
        assert result["timestamp"] == "2024-01-15"

    def test_date_custom_output_format(self):
        """Test custom output date format."""
        report = NormalizationReport()
        config = {
            "date_columns": ["date"],
            "output_date_format": "%m/%d/%Y",
        }
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"date": "2024-01-15"}
        result = step.process_row(row)

        assert result["date"] == "01/15/2024"

    def test_date_custom_input_formats(self):
        """Test custom input date formats."""
        report = NormalizationReport()
        config = {
            "date_columns": ["date"],
            "date_formats": ["%d-%b-%Y", "%Y%m%d"],
        }
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row1 = {"date": "15-Jan-2024"}
        result1 = step.process_row(row1)
        assert result1["date"] == "2024-01-15"

        row2 = {"date": "20240115"}
        result2 = step.process_row(row2)
        assert result2["date"] == "2024-01-15"

    def test_date_parsing_with_whitespace(self):
        """Test that date values are trimmed before parsing."""
        report = NormalizationReport()
        config = {"date_columns": ["date"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"date": "  2024-01-15  "}
        result = step.process_row(row)

        assert result["date"] == "2024-01-15"

    def test_date_invalid_value_non_strict(self):
        """Test invalid date value in non-strict mode."""
        report = NormalizationReport()
        config = {"date_columns": ["date"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"date": "not-a-date"}
        result = step.process_row(row)

        # Should preserve original value and add warning
        assert result["date"] == "not-a-date"
        assert len(report.warnings) > 0
        assert "Failed to parse date" in report.warnings[0]

    def test_date_invalid_value_strict_mode(self):
        """Test invalid date value in strict mode raises error."""
        report = NormalizationReport()
        config = {"date_columns": ["date"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=True)

        row = {"date": "invalid-date"}
        with pytest.raises(ValueError, match="Failed to parse date"):
            step.process_row(row)

    def test_date_column_with_none_value(self):
        """Test that None values are preserved in date columns."""
        report = NormalizationReport()
        config = {"date_columns": ["date"]}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"date": None}
        result = step.process_row(row)

        assert result["date"] is None


class TestBasicTypeCleanupStepCombined:
    """Test combined functionality with multiple type cleanups."""

    def test_multiple_type_columns_in_one_row(self):
        """Test processing multiple type columns in a single row."""
        report = NormalizationReport()
        config = {
            "numeric_columns": ["salary"],
            "boolean_columns": ["active"],
            "date_columns": ["hire_date"],
        }
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {
            "name": "Alice",
            "salary": "85,000",
            "active": "yes",
            "hire_date": "2024-01-15",
        }
        result = step.process_row(row)

        assert result["name"] == "Alice"
        assert result["salary"] == "85000"
        assert result["active"] is True
        assert result["hire_date"] == "2024-01-15"

    def test_no_type_columns_configured(self):
        """Test that row passes through unchanged when no columns configured."""
        report = NormalizationReport()
        config = {}
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"name": "Alice", "salary": "85,000", "active": "yes"}
        result = step.process_row(row)

        assert result == row

    def test_column_not_in_row(self):
        """Test that missing columns don't cause errors."""
        report = NormalizationReport()
        config = {
            "numeric_columns": ["salary", "bonus"],
            "boolean_columns": ["active"],
        }
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"salary": "85,000", "name": "Alice"}
        result = step.process_row(row)

        assert result["salary"] == "85000"
        assert result["name"] == "Alice"
        # bonus and active are not in the row, so they're not in result either
        assert "bonus" not in result
        assert "active" not in result

    def test_empty_row(self):
        """Test processing an empty row."""
        report = NormalizationReport()
        config = {
            "numeric_columns": ["salary"],
            "boolean_columns": ["active"],
        }
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {}
        result = step.process_row(row)

        assert result == {}

    def test_all_values_none(self):
        """Test processing a row where all values are None."""
        report = NormalizationReport()
        config = {
            "numeric_columns": ["salary"],
            "boolean_columns": ["active"],
            "date_columns": ["hire_date"],
        }
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"salary": None, "active": None, "hire_date": None}
        result = step.process_row(row)

        assert result["salary"] is None
        assert result["active"] is None
        assert result["hire_date"] is None

    def test_multiple_warnings_accumulate(self):
        """Test that multiple parsing failures accumulate warnings."""
        report = NormalizationReport()
        config = {
            "numeric_columns": ["salary"],
            "boolean_columns": ["active"],
            "date_columns": ["hire_date"],
            "convert_to_number": True,  # Enable conversion to trigger numeric warnings
        }
        step = BasicTypeCleanupStep(config=config, report=report, strict=False)

        row = {"salary": "invalid", "active": "maybe", "hire_date": "bad-date"}
        _ = step.process_row(row)

        assert len(report.warnings) == 3
        assert any("numeric" in w for w in report.warnings)
        assert any("boolean" in w for w in report.warnings)
        assert any("date" in w for w in report.warnings)
