"""
Priority 3: Edge case and performance tests.
Tests unusual data conditions and performance characteristics.
"""
import pandas as pd
import numpy as np
import pytest
import time

from featureleak import LeakScanner


class TestEdgeCasesDataTypes:
    """Tests for edge cases with unusual data types."""

    def test_all_numeric_columns(self):
        """Test dataset with only numeric columns."""
        df = pd.DataFrame({
            col: np.random.randn(100)
            for col in ["a", "b", "c", "d", "target"]
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)
        assert len(report.issues) >= 0

    def test_all_categorical_columns(self):
        """Test dataset with only categorical columns."""
        df = pd.DataFrame({
            "cat_a": np.random.choice(["x", "y", "z"], 50),
            "cat_b": np.random.choice(["a", "b", "c", "d"], 50),
            "cat_c": np.random.choice(["p", "q"], 50),
            "target": np.random.randint(0, 2, 50),
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_mixed_dtypes(self):
        """Test dataset with mixed dtypes."""
        df = pd.DataFrame({
            "int_col": [1, 2, 3, 4, 5],
            "float_col": [1.1, 2.2, 3.3, 4.4, 5.5],
            "str_col": ["a", "b", "c", "d", "e"],
            "bool_col": [True, False, True, False, True],
            "datetime_col": pd.date_range("2024-01-01", periods=5),
            "target": [0, 1, 0, 1, 0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_large_integer_values(self):
        """Test with very large integer values."""
        df = pd.DataFrame({
            "big_int": [10**15, 10**15 + 1, 10**15 + 2, 10**15 + 3, 10**15 + 4],
            "feature": [1, 2, 3, 4, 5],
            "target": [0, 1, 0, 1, 0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_float_edge_cases(self):
        """Test with special float values."""
        df = pd.DataFrame({
            "inf_col": [np.inf, -np.inf, 1.0, 2.0, 3.0],
            "nan_col": [np.nan, 1.0, 2.0, 3.0, 4.0],
            "zero_col": [0.0, 0.0, 0.0, 0.0, 0.0],
            "target": [0, 1, 0, 1, 0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_string_with_special_characters(self):
        """Test with special characters in strings."""
        df = pd.DataFrame({
            "text": [
                "normal text",
                "text with 日本語",
                "emoji 😀",
                "line\nbreak",
                "\t\ttabs",
            ],
            "target": [0, 1, 0, 1, 0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)


class TestEdgeCasesMissingness:
    """Tests for edge cases with missing data."""

    def test_all_missing_column(self):
        """Test column that is completely missing."""
        df = pd.DataFrame({
            "all_nan": [np.nan] * 10,
            "feature": list(range(10)),
            "target": [0, 1] * 5,
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_mostly_missing_column(self):
        """Test column that is mostly missing."""
        df = pd.DataFrame({
            "mostly_nan": [2.0] + [np.nan] * 99,
            "feature": list(range(100)),
            "target": [0, 1] * 50,
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_single_value_column(self):
        """Test column with single unique value."""
        df = pd.DataFrame({
            "constant": [42] * 100,
            "target": [0, 1] * 50,
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="constant", show_summary=False)
        
        # Should handle gracefully even though target has no variance
        assert isinstance(report, object)

    def test_alternating_missing_pattern(self):
        """Test alternating missing value pattern."""
        df = pd.DataFrame({
            "alternating": [1.0 if i % 2 == 0 else np.nan for i in range(100)],
            "target": list(range(100)),
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)


class TestEdgeCasesDatasetSize:
    """Tests for edge cases with dataset sizes."""

    def test_single_row_dataset(self):
        """Test with single row."""
        df = pd.DataFrame({
            "feature": [1],
            "target": [0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_single_column_dataset(self):
        """Test with single column (only target)."""
        df = pd.DataFrame({
            "target": [0, 1, 0, 1, 0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_two_row_dataset(self):
        """Test with minimal dataset."""
        df = pd.DataFrame({
            "a": [1, 2],
            "target": [0, 1],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_very_many_columns(self):
        """Test with many columns."""
        df = pd.DataFrame({
            f"col_{i}": np.random.randn(50)
            for i in range(1000)
        })
        df["target"] = np.random.randint(0, 2, 50)
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert report.metadata["n_columns"] == 1001


class TestEdgeCasesTargetVariable:
    """Tests for edge cases with target variable."""

    def test_binary_target(self):
        """Test with standard binary target."""
        df = pd.DataFrame({
            "feature": list(range(100)),
            "target": [0, 1] * 50,
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_multiclass_target(self):
        """Test with multiclass target."""
        df = pd.DataFrame({
            "feature": list(range(100)),
            "target": [0, 1, 2, 3] * 25,
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_continuous_target(self):
        """Test with continuous target."""
        df = pd.DataFrame({
            "feature": np.random.randn(100),
            "target": np.random.randn(100),
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_target_with_extreme_values(self):
        """Test target with extreme values."""
        df = pd.DataFrame({
            "feature": list(range(10)),
            "target": [10**10, 10**10 + 1, 0, -10**10] + list(range(6)),
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)


class TestEdgeCasesColumnNames:
    """Tests for edge cases with column names."""

    def test_numeric_column_names(self):
        """Test with numeric column names."""
        df = pd.DataFrame({
            0: [1, 2, 3],
            1: [4, 5, 6],
            2: [0, 1, 0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target=2, show_summary=False)
        
        assert isinstance(report, object)

    def test_special_character_column_names(self):
        """Test with special characters in column names."""
        df = pd.DataFrame({
            "col-with-dash": [1, 2, 3],
            "col.with.dot": [4, 5, 6],
            "col with space": [0, 1, 0],
            "col_with_emoji_😀": [7, 8, 9],
            "target": [0, 1, 0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_unicode_column_names(self):
        """Test with unicode column names."""
        df = pd.DataFrame({
            "feature_α": [1, 2, 3],
            "feature_β": [4, 5, 6],
            "feature_中文": [7, 8, 9],
            "target": [0, 1, 0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_very_long_column_names(self):
        """Test with very long column names."""
        df = pd.DataFrame({
            "a" * 1000: [1, 2, 3],
            "b" * 1000: [4, 5, 6],
            "target": [0, 1, 0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)


class TestPerformance:
    """Performance and resource utilization tests."""

    def test_scan_completes_in_reasonable_time_small(self):
        """Test that small dataset scans complete quickly."""
        df = pd.DataFrame({
            f"feature_{i}": np.random.randn(100)
            for i in range(10)
        })
        df["target"] = np.random.randint(0, 2, 100)
        
        scanner = LeakScanner()
        
        start = time.time()
        report = scanner.analyse(df, target="target", show_summary=False)
        elapsed = time.time() - start
        
        # Should complete in reasonable time (adjust if too strict)
        assert elapsed < 30  # 30 seconds for 100 rows, 10 columns
        assert isinstance(report, object)

    def test_scan_with_sampling_faster(self):
        """Test that sampling actually speeds up scanning."""
        df = pd.DataFrame({
            "feature": np.random.randn(50000),
            "target": np.random.randint(0, 2, 50000),
        })
        
        # Test with sampling
        scanner_sampled = LeakScanner(sample_size=1000)
        start = time.time()
        report_sampled = scanner_sampled.analyse(df, target="target", show_summary=False)
        time_sampled = time.time() - start
        
        # Sampled should be much faster
        assert time_sampled < 30
        assert report_sampled.metadata["n_rows"] == 1000

    def test_multiple_scans_consistent(self):
        """Test that multiple scans produce consistent results."""
        df = pd.DataFrame({
            "feature": list(range(100)),
            "target": [0, 1] * 50,
        })
        
        scanner = LeakScanner()
        
        report1 = scanner.analyse(df, target="target", show_summary=False)
        report2 = scanner.analyse(df, target="target", show_summary=False)
        
        # Same structure (won't be byte-for-byte identical due to timing)
        assert len(report1.issues) == len(report2.issues)
        assert len(report1.metadata) == len(report2.metadata)


class TestRobustness:
    """Tests for robustness to unexpected inputs."""

    def test_duplicate_column_names(self):
        """Test handling of duplicate column names."""
        # pandas will rename duplicates automatically
        df = pd.DataFrame([[1, 2, 3]], columns=["a", "a", "target"])
        
        scanner = LeakScanner()
        # Should handle even if columns are renamed by pandas
        try:
            report = scanner.analyse(df, target="target", show_summary=False)
            assert isinstance(report, object)
        except (ValueError, KeyError):
            # Expected if not handled
            pass

    def test_whitespace_values(self):
        """Test handling of whitespace values."""
        df = pd.DataFrame({
            "text": [" ", "  ", "\t", "\n", "normal"],
            "feature": [1, 2, 3, 4, 5],
            "target": [0, 1, 0, 1, 0],
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        assert isinstance(report, object)

    def test_infinite_recursion_prevention(self):
        """Test prevention of infinite loops in analysis."""
        # Create data with potential circular pattern
        df = pd.DataFrame({
            "a": list(range(100)),
            "b": [x % 10 for x in range(100)],
            "c": list(range(0, 1000, 10)),
            "target": [0, 1] * 50,
        })
        
        scanner = LeakScanner()
        report = scanner.analyse(df, target="target", show_summary=False)
        
        # Should complete without hanging
        assert isinstance(report, object)
