# tests/test_column_detector_id.py
import pandas as pd
import numpy as np
from preprocessing.core.column_detector import ColumnDetector


class TestIDDetection:
    """Test suite for enterprise ID detection"""
    
    def test_salesforce_id_detection(self):
        """Test Salesforce ID pattern recognition"""
        df = pd.DataFrame({
            'Id': ['0015000000Zs3LL', '0035000000Zs3MM', '00Q5000000Zs3NN'] * 10,
            'Name': ['John Doe', 'Jane Smith', 'Bob Johnson'] * 10,
            'AccountId': ['0015000000Zs3LL', '0015000000Zs3MM', '0015000000Zs3NN'] * 10,
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result is not None
        assert result.column_name == 'Id'
        assert result.detected_system == 'salesforce'
        assert result.score > 20  # Should score very high
        
    def test_salesforce_18_char_ids(self):
        """Test Salesforce 18-character ID format"""
        df = pd.DataFrame({
            'record_id': ['a025000000Zs3LLAAZ', 'a035000000Zs3MMAAZ', 'a0Q5000000Zs3NNAAZ'] * 50,
            'name': ['Product A', 'Product B', 'Product C'] * 50,
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'record_id'
        assert result.detected_system == 'salesforce'
        
    def test_hubspot_numeric_ids(self):
        """Test HubSpot numeric ID detection"""
        df = pd.DataFrame({
            'objectId': [1234567, 2345678, 3456789, 4567890, 5678901] * 20,
            'email': ['test1@example.com', 'test2@example.com', 'test3@example.com', 
                     'test4@example.com', 'test5@example.com'] * 20,
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'objectId'
        assert result.detected_system == 'hubspot'
        
    def test_dynamics_guid_detection(self):
        """Test Microsoft Dynamics GUID detection"""
        df = pd.DataFrame({
            'entityid': [
                'f47ac10b-58cc-4372-a567-0e02b2c3d479',
                '6ba7b810-9dad-11d1-80b4-00c04fd430c8',
                '6ba7b811-9dad-11d1-80b4-00c04fd430c8',
            ] * 30,
            'name': ['Entity A', 'Entity B', 'Entity C'] * 30,
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'entityid'
        assert result.detected_system == 'dynamics'
        
    def test_sap_padded_numeric(self):
        """Test SAP padded numeric IDs"""
        df = pd.DataFrame({
            'Material': ['0000012345', '0000023456', '0000034567'] * 40,
            'Description': ['Material A', 'Material B', 'Material C'] * 40,
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'Material'
        # May detect as SAP or generic depending on scoring
        
    def test_mongodb_objectid(self):
        """Test MongoDB ObjectId detection"""
        df = pd.DataFrame({
            '_id': ['507f1f77bcf86cd799439011', '507f191e810c19729de860ea', 
                   '507f191e810c19729de860eb'] * 25,
            'user': ['user1', 'user2', 'user3'] * 25,
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == '_id'
        assert result.detected_system == 'mongodb'
        
    def test_stripe_payment_ids(self):
        """Test Stripe payment system IDs"""
        df = pd.DataFrame({
            'customer_id': ['cus_4fdAW5ftNQow1a', 'cus_4fdAW5ftNQow1b', 
                           'cus_4fdAW5ftNQow1c'] * 20,
            'charge_id': ['ch_4fdAW5ftNQow1a', 'ch_4fdAW5ftNQow1b', 
                         'ch_4fdAW5ftNQow1c'] * 20,
        })
        
        result = ColumnDetector.detect_id_column(df)
        # Should prefer customer_id due to name pattern
        assert result.column_name == 'customer_id'
        assert result.detected_system == 'stripe'
        
    def test_sequential_numeric_ids(self):
        """Test sequential numeric ID detection"""
        df = pd.DataFrame({
            'ID': list(range(1000, 1100)),
            'Name': [f'Record {i}' for i in range(100)],
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'ID'
        assert 'sequential_numeric' in result.pattern_matches
        
    def test_uuid_standard_format(self):
        """Test standard UUID detection"""
        import uuid
        df = pd.DataFrame({
            'unique_id': [str(uuid.uuid4()) for _ in range(50)],
            'data': ['some data'] * 50,
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'unique_id'
        assert result.detected_system == 'uuid'
        
    def test_mixed_case_columns(self):
        """Test handling of mixed case column names"""
        df = pd.DataFrame({
            'RecordID': ['REC001', 'REC002', 'REC003'] * 30,
            'recordName': ['Name1', 'Name2', 'Name3'] * 30,
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'RecordID'
        
    def test_low_uniqueness_rejection(self):
        """Test rejection of columns with low uniqueness"""
        df = pd.DataFrame({
            'status_id': [1, 2, 3, 1, 2, 3] * 20,  # Only 3 unique values
            'row_number': list(range(120)),
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'row_number'
        assert result.uniqueness_ratio > 0.99
        
    def test_null_value_handling(self):
        """Test handling of columns with null values"""
        df = pd.DataFrame({
            'id_with_nulls': [1, 2, 3, None, 5, None] * 20,
            'complete_id': list(range(120)),
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'complete_id'
        # id_with_nulls should have penalty for nulls
        
    def test_international_id_names(self):
        """Test international column names"""
        df = pd.DataFrame({
            '顧客ID': list(range(100)),  # Japanese "Customer ID"
            'Kundennummer': list(range(100, 200)),  # German "Customer Number"
            'Identifiant': list(range(200, 300)),  # French "Identifier"
        })
        
        result = ColumnDetector.detect_id_column(df)
        # Should still detect based on uniqueness even without pattern match
        assert result is not None
        assert result.uniqueness_ratio > 0.99
        
    def test_composite_scoring(self):
        """Test that scoring considers multiple factors"""
        df = pd.DataFrame({
            'maybe_id': ['A', 'B', 'C', 'D', 'E'] * 20,  # Decent uniqueness
            'record_id': [f'REC{i:04d}' for i in range(100)],  # Better name + pattern
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'record_id'  # Better overall score
        
    def test_report_generation(self):
        """Test comprehensive report generation"""
        df = pd.DataFrame({
            'ID': list(range(100)),
            'UserID': [f'U{i:03d}' for i in range(100)],
            'Name': ['John', 'Jane', 'Bob'] * 33 + ['Alice'],
            'Status': ['Active', 'Inactive'] * 50,
        })
        
        report = ColumnDetector.generate_id_selection_report(df)
        
        assert report['selected'] in ['ID', 'UserID']
        assert report['confidence'] in ['high', 'medium', 'low']
        assert len(report['all_candidates']) > 0
        assert 'recommendation' in report
        
    def test_edge_case_empty_dataframe(self):
        """Test handling of empty DataFrame"""
        df = pd.DataFrame()
        result = ColumnDetector.detect_id_column(df)
        assert result is None
        
    def test_edge_case_no_valid_ids(self):
        """Test handling when no valid ID columns exist"""
        df = pd.DataFrame({
            'color': ['red', 'blue', 'green'] * 10,
            'size': ['small', 'medium', 'large'] * 10,
            'category': ['A', 'B', 'A', 'B'] * 7 + ['C', 'C'],
        })
        
        result = ColumnDetector.detect_id_column(df)
        # Should return something but with low score and issues
        if result:
            assert result.score < 5
            assert len(result.issues) > 0
            
    def test_snowflake_ids(self):
        """Test Twitter/Discord style Snowflake IDs"""
        df = pd.DataFrame({
            'tweet_id': ['1234567890123456789', '1234567890123456790', 
                        '1234567890123456791'] * 20,
            'user': ['user1', 'user2', 'user3'] * 20,
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'tweet_id'
        assert result.detected_system == 'snowflake'
        
    def test_consistent_length_bonus(self):
        """Test that consistent length IDs get bonus points"""
        df = pd.DataFrame({
            'variable_id': ['A1', 'BB22', 'CCC333', 'DDDD4444'] * 25,
            'fixed_id': ['ID001', 'ID002', 'ID003', 'ID004'] * 25,
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'fixed_id'
        assert 'consistent_length' in result.pattern_matches


class TestRealWorldScenarios:
    """Test with realistic data patterns"""
    
    def test_salesforce_export(self):
        """Test typical Salesforce export format"""
        df = pd.DataFrame({
            'Id': ['0035000000Zs3MM'] * 50 + ['0035000000Zs3NN'] * 50,
            'Name': ['Acme Corp'] * 50 + ['Globex Inc'] * 50,
            'AccountId': ['0015000000Zs3LL'] * 100,
            'CreatedDate': pd.date_range('2023-01-01', periods=100),
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'Id'
        # Note: This has low uniqueness but strong pattern match
        
    def test_messy_real_data(self):
        """Test with messy, real-world style data"""
        df = pd.DataFrame({
            'cust_num': ['CUST-001', 'CUST-002', np.nan, 'CUST-003', ''] * 20,
            'RECORD ID': [f'{i:06d}' for i in range(100)],
            'email': ['test@example.com', None, 'user@test.com'] * 33 + ['admin@test.com'],
        })
        
        result = ColumnDetector.detect_id_column(df)
        assert result.column_name == 'RECORD ID'  # Despite weird spacing
        
    def test_multiple_id_columns(self):
        """Test when multiple ID columns exist"""
        df = pd.DataFrame({
            'customer_id': [f'C{i:04d}' for i in range(100)],
            'order_id': [f'O{i:04d}' for i in range(100)],
            'transaction_id': [f'T{i:04d}' for i in range(100)],
            'name': ['Customer'] * 100,
        })
        
        result = ColumnDetector.detect_id_column(df)
        # Should pick one based on name priority
        assert result.column_name in ['customer_id', 'order_id', 'transaction_id']
        assert result.score > 15  # All should score well