import pytest
import pandas as pd
import numpy as np
import logging
import json
import io
from pathlib import Path
from DataCleaningPipeline.pipeline import DataCleaningPipeline, SchemaValidator, PipelineError, ValidationError
from DataCleaningPipeline.config import PipelineConfig

class TestAuditPhase3:
    
    def test_json_logging(self):
        """Test that logs are valid JSON"""
        log_capture = io.StringIO()
        handler = logging.StreamHandler(log_capture)
        
        # Manually verify my JsonFormatter since logger setup is strict
        from DataCleaningPipeline.pipeline import JsonFormatter
        formatter = JsonFormatter()
        handler.setFormatter(formatter)
        
        logger = logging.getLogger("test_json_logger")
        logger.addHandler(handler)
        logger.setLevel(logging.INFO)
        
        logger.info("Test message with quotes \" and newline \n")
        
        log_output = log_capture.getvalue().strip()
        print(f"\nLog Output: {log_output}")
        
        # Verify it parses as JSON
        log_obj = json.loads(log_output)
        assert log_obj['message'] == "Test message with quotes \" and newline \n"
        assert log_obj['level'] == 'INFO'

    def test_schema_regex_datetime(self, tmp_path):
        """Test enhanced schema validation"""
        import yaml
        
        schema = {
            'test_schema': {
                'columns': {
                    'email': {
                        'type': 'string',
                        'pattern': r'^[\w\.-]+@[\w\.-]+\.\w+$'
                    },
                    'date': {
                        'type': 'datetime'
                    }
                }
            }
        }
        
        schema_path = tmp_path / "schema.yaml"
        with open(schema_path, 'w') as f:
            yaml.dump(schema, f)
            
        # Good Data
        df = pd.DataFrame({
            'email': ['test@example.com', 'user.name@domain.co'],
            'date': pd.to_datetime(['2023-01-01', '2023-01-02'])
        })
        
        validator = SchemaValidator(schema_path)
        result = validator.validate(df, 'test_schema')
        assert result.is_valid, f"Should be valid: {result.errors}"
        
        # Bad Data
        df_bad = pd.DataFrame({
            'email': ['bad-email', 'valid@example.com'], # One bad
            'date': ['not-a-date', '2023-01-02']     # One bad type (object)
        })
        
        result_bad = validator.validate(df_bad, 'test_schema')
        assert not result_bad.is_valid
        assert any("pattern" in e for e in result_bad.errors)
        assert any("datetime" in e for e in result_bad.errors)

    def test_error_consistency(self):
        """Test that strict mode raises PipelineError"""
        config = PipelineConfig(strict_mode=True)
        # Create invalid df to force error in internal ops if possible, 
        # or mock something to fail.
        
        df = pd.DataFrame({'a': [1, 2]})
        pipeline = DataCleaningPipeline(df, config)
        
        # Force a failure in standard operation that usually raises generic Exception
        # We can try to clean a column that doesn't exist? That raises ValidationError 
        # which should be re-raised as ValidationError (subclass of PipelineError)
        
        with pytest.raises(ValidationError):
            pipeline.validate_email('non_existent_col')
            
        # Try to force a Runtime error in apply_custom_function?
        def fail_func(x):
            raise ValueError("Boom")
            
        # In strict_mode, this should raise PipelineError wrapping ValueError
        # UNLESS apply_custom_function handle_errors=True catches it strictly?
        # apply_custom_function uses _safe_operation.
        # Inside apply_custom_function, valid code catches exc and logs warning if handle_errors=True.
        # But if handle_errors=False, it should bubble up to _safe_operation.
        
        config_funcs = PipelineConfig(strict_mode=True, allow_custom_functions=True)
        pipeline_funcs = DataCleaningPipeline(df, config_funcs)
        
        with pytest.raises(PipelineError) as exc:
            # handle_errors=False means custom func exception bubbles up
            pipeline_funcs.apply_custom_function('a', fail_func, handle_errors=False)
            
        assert "apply_custom_function failed" in str(exc.value)
