import pytest
import pandas as pd
import numpy as np
from mlguardian import SchemaBuilder, Schema, ColumnType


class TestSchemaBuilder:
    def test_basic_inference(self):
        df = pd.DataFrame({
            'int_col': [1, 2, 3],
            'float_col': [1.1, 2.2, 3.3],
            'str_col': ['a', 'b', 'c'],
            'bool_col': [True, False, True]
        })
        
        builder = SchemaBuilder()
        schema = builder.infer_schema(df)
        
        assert schema.columns['int_col'].dtype == ColumnType.INTEGER
        assert schema.columns['float_col'].dtype == ColumnType.FLOAT
        assert schema.columns['str_col'].dtype == ColumnType.CATEGORICAL  # Low cardinality
        assert schema.columns['bool_col'].dtype == ColumnType.BOOLEAN
    
    def test_categorical_detection(self):
        df = pd.DataFrame({
            'high_card': list(range(100)),  # Should be integer, not categorical
            'low_card': ['cat'] * 50 + ['dog'] * 50  # Should be categorical
        })
        
        builder = SchemaBuilder(categorical_threshold=50)
        schema = builder.infer_schema(df)
        
        assert schema.columns['high_card'].dtype == ColumnType.INTEGER
        assert schema.columns['low_card'].dtype == ColumnType.CATEGORICAL
    
        def test_target_detection(self):
            df = pd.DataFrame({
                'feature1': [1, 2, 3, 4],
                'feature2': ['a', 'b', 'c', 'd'],
                'target': [0, 1, 0, 1]  # Binary, balanced  
            })

            builder = SchemaBuilder()
            
            # Test 1: Explicit target column
            schema = builder.infer_schema(df, target_col='target')
            assert schema.columns['target'].dtype == ColumnType.TARGET
            assert schema.target_name == 'target'
            
            # Test 2: Auto-detection (heuristic-based)
            schema_auto = builder.infer_schema(df)  # No target specified
            # The heuristic might or might not detect it depending on the data
            # Just verify the schema is created successfully
            assert 'target' in schema_auto.columns
    
    def test_semantic_detection(self):
        df = pd.DataFrame({
            'emails': ['user1@test.com', 'user2@test.com', 'user3@test.com'],
            'urls': ['http://test.com', 'https://test2.com', 'http://test3.com'],
            'phones': ['123-456-7890', '098-765-4321', '111-222-3333']
        })
        
        builder = SchemaBuilder()
        schema = builder.infer_schema(df)
        
        assert schema.columns['emails'].semantic_type == 'email'
        assert schema.columns['urls'].semantic_type == 'url'
        assert schema.columns['phones'].semantic_type == 'phone'


class TestSchemaValidation:
    def test_validation_pass(self):
        df = pd.DataFrame({
            'num': [1, 2, 3],
            'cat': ['a', 'b', 'a']
        })
        
        builder = SchemaBuilder()
        schema = builder.infer_schema(df)
        
        # Should pass against itself
        violations = schema.validate(df, raise_error=False)
        assert len(violations) == 0
    
    def test_type_mismatch(self):
        df_ref = pd.DataFrame({'col': [1, 2, 3]})
        df_test = pd.DataFrame({'col': ['a', 'b', 'c']})
        
        builder = SchemaBuilder()
        schema = builder.infer_schema(df_ref)
        
        violations = schema.validate(df_test, raise_error=False)
        assert 'col' in violations
    
    def test_missing_column(self):
        df_ref = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})
        df_test = pd.DataFrame({'col1': [1, 2]})
        
        builder = SchemaBuilder()
        schema = builder.infer_schema(df_ref)
        
        violations = schema.validate(df_test, raise_error=False)
        assert '__missing__' in violations


class TestSchemaSerialization:
    def test_json_roundtrip(self):
        df = pd.DataFrame({
            'num': [1.0, 2.0, 3.0],
            'cat': ['a', 'b', 'c']
        })
        
        builder = SchemaBuilder()
        schema = builder.infer_schema(df)
        
        json_str = schema.to_json()
        schema2 = Schema.from_json(json_str)
        
        assert len(schema2.columns) == len(schema.columns)
        assert schema2.columns['num'].dtype == ColumnType.FLOAT