"""
Enhanced Issue Detector - Level 2.5 with structural anomaly detection.

Extends base detection with:
- Schema drift detection
- Type drift detection
- Column explosion detection
- Category explosion
- Mixed delimiters
- Broken quoting
- Nested JSON
- Encoding mismatches
"""

from typing import List, Dict, Set, Any
from dataclasses import dataclass
import pandas as pd
import json
import re


@dataclass
class StructuralAnomaly:
    """Structural anomaly detected."""
    anomaly_type: str
    severity: str  # 'low', 'medium', 'high', 'critical'
    description: str
    affected_columns: List[str]
    suggested_fix: str
    metadata: Dict[str, Any]


class EnhancedIssueDetector:
    """Enhanced detector with structural anomaly detection."""
    
    def detect_schema_drift(
        self,
        dataframes: Dict[str, pd.DataFrame]
    ) -> List[StructuralAnomaly]:
        """Detect schema drift across multiple files."""
        anomalies = []
        
        if len(dataframes) < 2:
            return anomalies
        
        # Get all unique column sets
        column_sets = {name: set(df.columns) for name, df in dataframes.items()}
        
        # Find common columns
        all_columns = set().union(*column_sets.values())
        common_columns = set.intersection(*column_sets.values())
        
        # Detect missing columns
        for col in all_columns - common_columns:
            files_with_col = [name for name, cols in column_sets.items() if col in cols]
            files_without = [name for name, cols in column_sets.items() if col not in cols]
            
            anomalies.append(StructuralAnomaly(
                anomaly_type='missing_column_across_files',
                severity='high',
                description=f"Column '{col}' missing in {len(files_without)} files",
                affected_columns=[col],
                suggested_fix='Add missing column or remove from others',
                metadata={
                    'files_with': files_with_col,
                    'files_without': files_without
                }
            ))
        
        return anomalies
    
    def detect_type_drift(
        self,
        dataframes: Dict[str, pd.DataFrame]
    ) -> List[StructuralAnomaly]:
        """Detect type drift for same columns across files."""
        anomalies = []
        
        # Get common columns
        if not dataframes:
            return anomalies
        
        common_cols = set.intersection(*[set(df.columns) for df in dataframes.values()])
        
        for col in common_cols:
            types = {}
            for name, df in dataframes.items():
                if col in df.columns:
                    types[name] = str(df[col].dtype)
            
            # Check if types differ
            unique_types = set(types.values())
            if len(unique_types) > 1:
                anomalies.append(StructuralAnomaly(
                    anomaly_type='type_drift',
                    severity='high',
                    description=f"Column '{col}' has {len(unique_types)} different types",
                    affected_columns=[col],
                    suggested_fix='Convert all to string or most common type',
                    metadata={'types_by_file': types}
                ))
        
        return anomalies
    
    def detect_column_explosion(
        self,
        df: pd.DataFrame,
        expected_max_columns: int = 100
    ) -> List[StructuralAnomaly]:
        """Detect unexpected number of columns."""
        anomalies = []
        
        if len(df.columns) > expected_max_columns:
            anomalies.append(StructuralAnomaly(
                anomaly_type='column_explosion',
                severity='medium',
                description=f"File has {len(df.columns)} columns (>{expected_max_columns})",
                affected_columns=list(df.columns),
                suggested_fix='Check for delimiter issues or data format problems',
                metadata={'column_count': len(df.columns)}
            ))
        
        return anomalies
    
    def detect_category_explosion(
        self,
        df: pd.DataFrame,
        cardinality_threshold: float = 0.9
    ) -> List[StructuralAnomaly]:
        """Detect columns with too many unique values (likely not categorical)."""
        anomalies = []
        
        for col in df.select_dtypes(include=['object']).columns:
            unique_ratio = df[col].nunique() / len(df)
            
            if unique_ratio > cardinality_threshold:
                anomalies.append(StructuralAnomaly(
                    anomaly_type='category_explosion',
                    severity='low',
                    description=f"Column '{col}' has {df[col].nunique()} unique values",
                    affected_columns=[col],
                    suggested_fix='Consider if this should be categorical or text',
                    metadata={
                        'unique_count': df[col].nunique(),
                        'total_rows': len(df),
                        'ratio': unique_ratio
                    }
                ))
        
        return anomalies
    
    def detect_mixed_delimiters(
        self,
        file_path: str
    ) -> List[StructuralAnomaly]:
        """Detect if file has mixed delimiters (needs raw file access)."""
        anomalies = []
        
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                lines = f.readlines()[:100]  # Sample first 100 lines
            
            delimiter_counts = {
                ',': sum(line.count(',') for line in lines),
                ';': sum(line.count(';') for line in lines),
                '\t': sum(line.count('\t') for line in lines),
                '|': sum(line.count('|') for line in lines)
            }
            
            # Check if multiple delimiters have significant counts
            significant_delimiters = [d for d, count in delimiter_counts.items() if count > len(lines)]
            
            if len(significant_delimiters) > 1:
                anomalies.append(StructuralAnomaly(
                    anomaly_type='mixed_delimiters',
                    severity='critical',
                    description=f"File contains multiple delimiters: {significant_delimiters}",
                    affected_columns=[],
                    suggested_fix='Standardize to single delimiter',
                    metadata={'delimiter_counts': delimiter_counts}
                ))
        except Exception:
            pass
        
        return anomalies
    
    def detect_broken_quoting(
        self,
        df: pd.DataFrame
    ) -> List[StructuralAnomaly]:
        """Detect broken quote characters in strings."""
        anomalies = []
        
        for col in df.select_dtypes(include=['object']).columns:
            # Check for unmatched quotes
            sample = df[col].dropna().head(1000)
            
            broken_quotes = 0
            for val in sample:
                if isinstance(val, str):
                    # Count quotes
                    if val.count('"') % 2 != 0 or val.count("'") % 2 != 0:
                        broken_quotes += 1
            
            if broken_quotes > 0:
                anomalies.append(StructuralAnomaly(
                    anomaly_type='broken_quoting',
                    severity='medium',
                    description=f"Column '{col}' has {broken_quotes} rows with unmatched quotes",
                    affected_columns=[col],
                    suggested_fix='Fix quote escaping or remove quotes',
                    metadata={'broken_count': broken_quotes}
                ))
        
        return anomalies
    
    def detect_nested_json(
        self,
        df: pd.DataFrame
    ) -> List[StructuralAnomaly]:
        """Detect JSON objects nested in CSV cells."""
        anomalies = []
        
        for col in df.select_dtypes(include=['object']).columns:
            sample = df[col].dropna().head(100)
            
            json_count = 0
            for val in sample:
                if isinstance(val, str) and (val.startswith('{') or val.startswith('[')):
                    try:
                        json.loads(val)
                        json_count += 1
                    except:
                        pass
            
            if json_count > len(sample) * 0.1:  # >10% are JSON
                anomalies.append(StructuralAnomaly(
                    anomaly_type='nested_json',
                    severity='medium',
                    description=f"Column '{col}' contains nested JSON objects",
                    affected_columns=[col],
                    suggested_fix='Flatten JSON into separate columns',
                    metadata={'json_count': json_count, 'sample_size': len(sample)}
                ))
        
        return anomalies
    
    def detect_encoding_mismatch(
        self,
        file_paths: List[str]
    ) -> List[StructuralAnomaly]:
        """Detect encoding mismatches across files."""
        import chardet
        
        anomalies = []
        encodings = {}
        
        for path in file_paths:
            try:
                with open(path, 'rb') as f:
                    raw = f.read(10000)  # Sample first 10KB
                    result = chardet.detect(raw)
                    encodings[path] = result['encoding']
            except Exception:
                encodings[path] = 'unknown'
        
        unique_encodings = set(encodings.values())
        if len(unique_encodings) > 1:
            anomalies.append(StructuralAnomaly(
                anomaly_type='encoding_mismatch',
                severity='high',
                description=f"Files have {len(unique_encodings)} different encodings",
                affected_columns=[],
                suggested_fix='Convert all files to UTF-8',
                metadata={'encodings_by_file': encodings}
            ))
        
        return anomalies
    
    def detect_all_structural(
        self,
        dataframes: Dict[str, pd.DataFrame] = None,
        file_paths: List[str] = None,
        df: pd.DataFrame = None
    ) -> List[StructuralAnomaly]:
        """Run all structural anomaly detections."""
        all_anomalies = []
        
        if dataframes and len(dataframes) > 1:
            all_anomalies.extend(self.detect_schema_drift(dataframes))
            all_anomalies.extend(self.detect_type_drift(dataframes))
        
        if df is not None:
            all_anomalies.extend(self.detect_column_explosion(df))
            all_anomalies.extend(self.detect_category_explosion(df))
            all_anomalies.extend(self.detect_broken_quoting(df))
            all_anomalies.extend(self.detect_nested_json(df))
        
        if file_paths:
            all_anomalies.extend(self.detect_encoding_mismatch(file_paths))
            for path in file_paths:
                all_anomalies.extend(self.detect_mixed_delimiters(path))
        
        return all_anomalies
