"""
Test transformation operations.
"""

import pytest
import pandas as pd
import numpy as np

from app.transformations.cleaning import (
    drop_duplicates_pandas,
    fill_missing_pandas,
    remove_outliers_pandas,
)
from app.transformations.encoding import (
    onehot_encode_pandas,
    label_encode_pandas,
)
from app.transformations.normalization import (
    normalize_pandas,
    standardize_pandas,
)
from app.transformations.registry import TransformationRegistry


class TestCleaningTransformations:
    """Tests for cleaning transformations."""

    def test_drop_duplicates(self, sample_dataframe):
        """Test dropping duplicate rows."""
        result = drop_duplicates_pandas(sample_dataframe)
        
        # Original has 6 rows with 1 duplicate
        assert len(result) == 5
        assert len(result) < len(sample_dataframe)

    def test_fill_missing_mean(self):
        """Test filling missing values with mean."""
        df = pd.DataFrame({
            "age": [25, 30, None, 35, None],
        })
        
        result = fill_missing_pandas(df, "age", strategy="mean")
        
        assert result["age"].isna().sum() == 0
        expected_mean = (25 + 30 + 35) / 3
        assert result["age"].iloc[2] == pytest.approx(expected_mean)

    def test_fill_missing_median(self):
        """Test filling missing values with median."""
        df = pd.DataFrame({
            "age": [25, 30, None, 35, 40],
        })
        
        result = fill_missing_pandas(df, "age", strategy="median")
        
        assert result["age"].isna().sum() == 0
        assert result["age"].iloc[2] == 32.5  # Median of [25, 30, 35, 40]

    def test_fill_missing_constant(self):
        """Test filling missing values with constant."""
        df = pd.DataFrame({
            "age": [25, None, 35],
        })
        
        result = fill_missing_pandas(df, "age", strategy="constant", fill_value=0)
        
        assert result["age"].iloc[1] == 0

    def test_remove_outliers_iqr(self):
        """Test outlier removal using IQR method."""
        df = pd.DataFrame({
            "value": [1, 2, 3, 4, 5, 100],  # 100 is an outlier
        })
        
        result = remove_outliers_pandas(df, "value", method="iqr")
        
        assert len(result) < len(df)
        assert 100 not in result["value"].values


class TestEncodingTransformations:
    """Tests for encoding transformations."""

    def test_onehot_encode(self):
        """Test one-hot encoding."""
        df = pd.DataFrame({
            "color": ["red", "blue", "red", "green"],
        })
        
        result = onehot_encode_pandas(df, "color")
        
        # Should have 3 columns (red, blue, green)
        assert "color_red" in result.columns
        assert "color_blue" in result.columns
        assert "color_green" in result.columns
        assert "color" not in result.columns

    def test_label_encode(self):
        """Test label encoding."""
        df = pd.DataFrame({
            "size": ["small", "medium", "large", "small"],
        })
        
        result = label_encode_pandas(df, "size")
        
        # Should be numeric
        assert pd.api.types.is_numeric_dtype(result["size"])
        assert result["size"].nunique() == 3


class TestNormalizationTransformations:
    """Tests for normalization transformations."""

    def test_minmax_normalization(self):
        """Test min-max normalization."""
        df = pd.DataFrame({
            "value": [0, 25, 50, 75, 100],
        })
        
        result = normalize_pandas(df, "value", method="minmax")
        
        # Should be scaled to [0, 1]
        assert result["value"].min() == 0.0
        assert result["value"].max() == 1.0
        assert result["value"].iloc[2] == 0.5  # Middle value

    def test_zscore_normalization(self):
        """Test z-score normalization."""
        df = pd.DataFrame({
            "value": [10, 20, 30, 40, 50],
        })
        
        result = normalize_pandas(df, "value", method="zscore")
        
        # Z-score should have mean ~0 and std ~1
        assert result["value"].mean() == pytest.approx(0, abs=1e-10)
        assert result["value"].std() == pytest.approx(1, abs=1e-10)

    def test_standardize(self):
        """Test standardization."""
        df = pd.DataFrame({
            "value": [1, 2, 3, 4, 5],
        })
        
        result = standardize_pandas(df, "value")
        
        assert result["value"].mean() == pytest.approx(0, abs=1e-10)


class TestTransformationRegistry:
    """Tests for transformation registry."""

    def test_registry_initialization(self):
        """Test registry initializes with transformations."""
        registry = TransformationRegistry()
        
        assert "drop_duplicates" in registry.transformations
        assert "fill_missing" in registry.transformations
        assert "normalize" in registry.transformations

    def test_execute_transformation(self, sample_dataframe):
        """Test executing transformation through registry."""
        registry = TransformationRegistry()
        
        result = registry.execute(
            "drop_duplicates",
            sample_dataframe,
            {},
            engine="pandas",
        )
        
        assert len(result) < len(sample_dataframe)

    def test_engine_fallback(self, sample_dataframe):
        """Test automatic engine fallback."""
        registry = TransformationRegistry()
        
        # Request polars but operation only supports pandas
        result = registry.execute(
            "drop_duplicates",
            sample_dataframe,
            {},
            engine="pandas",  # Explicitly use pandas
        )
        
        assert isinstance(result, pd.DataFrame)

    def test_invalid_operation(self, sample_dataframe):
        """Test error handling for invalid operation."""
        from app.core.exceptions import InvalidTransformationError
        
        registry = TransformationRegistry()
        
        with pytest.raises(InvalidTransformationError):
            registry.execute(
                "nonexistent_operation",
                sample_dataframe,
                {},
                engine="pandas",
            )
