"""
Validation utilities for comparing Python vs SQL output
"""
import pandas as pd
from typing import Dict, List, Optional, Tuple
from utils.logger import logger


def compare_outputs(
    python_output: List[Dict],
    sql_output: List[Dict],
    key_columns: Optional[List[str]] = None
) -> Dict:
    """
    Compare Python output with SQL output
    
    Args:
        python_output: List of dicts from Python service
        sql_output: List of dicts from SQL query
        key_columns: Columns to use for matching rows
    
    Returns:
        Comparison report dict
    """
    python_df = pd.DataFrame(python_output)
    sql_df = pd.DataFrame(sql_output)
    
    report = {
        'python_count': len(python_output),
        'sql_count': len(sql_output),
        'count_match': len(python_output) == len(sql_output),
        'differences': [],
        'missing_in_python': [],
        'missing_in_sql': [],
        'column_differences': {}
    }
    
    if python_df.empty and sql_df.empty:
        report['status'] = 'both_empty'
        return report
    
    if python_df.empty:
        report['status'] = 'python_empty'
        return report
    
    if sql_df.empty:
        report['status'] = 'sql_empty'
        return report
    
    # Compare columns
    python_cols = set(python_df.columns)
    sql_cols = set(sql_df.columns)
    
    missing_in_python = sql_cols - python_cols
    missing_in_sql = python_cols - sql_cols
    
    if missing_in_python:
        report['missing_in_python'] = list(missing_in_python)
    if missing_in_sql:
        report['missing_in_sql'] = list(missing_in_sql)
    
    # Compare common columns
    common_cols = python_cols & sql_cols
    
    if key_columns:
        # Match rows by key columns
        for key_col in key_columns:
            if key_col not in common_cols:
                logger.warning(f"Key column {key_col} not found in both outputs")
                key_columns = None
                break
    
    if key_columns:
        # Merge on key columns
        python_key = python_df[key_columns].copy()
        sql_key = sql_df[key_columns].copy()
        
        merged = python_key.merge(
            sql_key,
            on=key_columns,
            how='outer',
            indicator=True
        )
        
        report['only_in_python'] = len(merged[merged['_merge'] == 'left_only'])
        report['only_in_sql'] = len(merged[merged['_merge'] == 'right_only'])
        report['in_both'] = len(merged[merged['_merge'] == 'both'])
    else:
        # Compare by index (assume same order)
        min_len = min(len(python_df), len(sql_df))
        report['compared_rows'] = min_len
    
    # Compare values in common columns
    numeric_cols = []
    text_cols = []
    
    for col in common_cols:
        if python_df[col].dtype in ['int64', 'float64'] or sql_df[col].dtype in ['int64', 'float64']:
            numeric_cols.append(col)
        else:
            text_cols.append(col)
    
    differences = []
    
    # Compare numeric columns
    for col in numeric_cols:
        if col in python_df.columns and col in sql_df.columns:
            python_vals = python_df[col].fillna(0)
            sql_vals = sql_df[col].fillna(0)
            
            # Handle different lengths
            min_len = min(len(python_vals), len(sql_vals))
            python_vals = python_vals[:min_len]
            sql_vals = sql_vals[:min_len]
            
            diff = (python_vals - sql_vals).abs()
            max_diff = diff.max()
            mean_diff = diff.mean()
            
            if max_diff > 0.01:  # Tolerance for floating point
                differences.append({
                    'column': col,
                    'type': 'numeric',
                    'max_difference': float(max_diff),
                    'mean_difference': float(mean_diff),
                    'different_count': int((diff > 0.01).sum())
                })
    
    # Compare text columns
    for col in text_cols:
        if col in python_df.columns and col in sql_df.columns:
            python_vals = python_df[col].fillna('').astype(str)
            sql_vals = sql_df[col].fillna('').astype(str)
            
            min_len = min(len(python_vals), len(sql_vals))
            python_vals = python_vals[:min_len]
            sql_vals = sql_vals[:min_len]
            
            different = (python_vals != sql_vals).sum()
            
            if different > 0:
                differences.append({
                    'column': col,
                    'type': 'text',
                    'different_count': int(different),
                    'total_compared': int(min_len)
                })
    
    report['differences'] = differences
    report['status'] = 'compared'
    
    return report


def print_comparison_report(report: Dict):
    """Print a formatted comparison report"""
    print("\n" + "="*60)
    print("PYTHON vs SQL OUTPUT COMPARISON")
    print("="*60)
    
    print(f"\nRow Counts:")
    print(f"  Python: {report['python_count']}")
    print(f"  SQL:    {report['sql_count']}")
    print(f"  Match:  {'✅' if report['count_match'] else '❌'}")
    
    if report.get('only_in_python'):
        print(f"\n  Only in Python: {report['only_in_python']}")
    if report.get('only_in_sql'):
        print(f"  Only in SQL: {report['only_in_sql']}")
    if report.get('in_both'):
        print(f"  In both: {report['in_both']}")
    
    if report.get('missing_in_python'):
        print(f"\n⚠️  Columns missing in Python: {report['missing_in_python']}")
    
    if report.get('missing_in_sql'):
        print(f"⚠️  Columns missing in SQL: {report['missing_in_sql']}")
    
    if report.get('differences'):
        print(f"\n📊 Column Differences:")
        for diff in report['differences']:
            print(f"\n  Column: {diff['column']} ({diff['type']})")
            if diff['type'] == 'numeric':
                print(f"    Max difference: {diff['max_difference']:.6f}")
                print(f"    Mean difference: {diff['mean_difference']:.6f}")
                print(f"    Different rows: {diff['different_count']}")
            else:
                print(f"    Different rows: {diff['different_count']} / {diff['total_compared']}")
    else:
        print("\n✅ No significant differences found!")
    
    print("\n" + "="*60 + "\n")

