import pandas as pd
import numpy as np
from datetime import datetime

def classify(val,lower,upper):
    if val < lower:
        return 'low'
    elif val > upper:
        return 'high'
    else:
        return 'none'

def create_full_calendar_and_interpolate(
        master_data,
        group_columns,
        variable,
        date_column,
        freq,
        min_records
    ):
    """
    Returns:
        final_df (pd.DataFrame): The cleaned, interpolated data.
        success_report (pd.DataFrame): Quality metrics for included groups.
        exclusion_report (pd.DataFrame): Reasons for excluded groups.
    """
    master_data[date_column] = pd.to_datetime(master_data[date_column])
    
    full_group_data = []
    success_metrics = []
    dropped_metrics = []

    for group_key, group in master_data.groupby(group_columns):
        group_name = str(group_key)
        initial_count = len(group)
        
        # --- Check 1: Min Records ---
        if initial_count < min_records:
            dropped_metrics.append({
                "group": group_name,
                "reason": "Below Min Records",
                "details": f"{initial_count} < {min_records}"
            })
            continue

        # --- Calendar Generation ---
        min_date, max_date = group[date_column].min(), group[date_column].max()
        full_dates = pd.date_range(start=min_date, end=max_date, freq=freq)
        
        calendar_dict = {col: group_key[i] if isinstance(group_key, tuple) else group_key
                         for i, col in enumerate(group_columns)}
        calendar_dict[date_column] = full_dates
        full_calendar = pd.DataFrame(calendar_dict)

        # --- Merge & Missing Check ---
        merged = full_calendar.merge(group, on=group_columns + [date_column], how="left")
        interpolated_count = merged[variable].isna().sum()
        total_len = len(merged)
        interpolation_rate = interpolated_count / total_len

        # --- Check 2: Max Interpolation ---
        if interpolation_rate > 0.25:
            dropped_metrics.append({
                "group": group_name,
                "reason": "High Interpolation",
                "details": f"{interpolation_rate:.1%} > 25%"
            })
            continue

        # --- Success: Log Metrics & Interpolate ---
        success_metrics.append({
            "group": group_name,
            "initial_record_count": initial_count,
            "interpolated_record_count": interpolated_count,
            "total_final_records": total_len,
            "interpolation_pct": round(interpolation_rate * 100, 2)
        })

        merged["is_missing_record"] = merged[variable].isna()
        numeric_cols = merged.select_dtypes(include=[np.number]).columns
        for col in numeric_cols:
            merged[col] = merged[col].interpolate(method="linear", limit_direction="both")

        full_group_data.append(merged)

    # Convert to DataFrames
    final_df = pd.concat(full_group_data, ignore_index=True) if full_group_data else pd.DataFrame()
    success_report = pd.DataFrame(success_metrics)
    exclusion_report = pd.DataFrame(dropped_metrics)
    
    return final_df, success_report, exclusion_report


def print_anomaly_stats(df, group_columns):
    # Calculate global stats
    total_records = len(df)
    # Ensure is_anomaly is treated as boolean for counting
    total_anomalies = df['is_Anomaly'].fillna(False).astype(bool).sum()
    anomaly_rate = (total_anomalies / total_records) * 100

    print("\n" + "="*45)
    print(f"{'ANOMALY DETECTION SUMMARY':^45}")
    print("="*45)
    print(f"{'Total Records:':<25} {total_records:,}")
    print(f"{'Total Anomalies:':<25} {total_anomalies:,}")
    print(f"{'Overall Anomaly Rate:':<25} {anomaly_rate:.2f}%")
    print("-" * 45)

    # --- CHANGE START: Group by Rate ---
    print(f"Top 5 Groups by Anomaly Rate ({' > '.join(group_columns)}):")
    
    # 1. Group by keys
    # 2. Calculate mean (rate) and count (to show absolute numbers too)
    group_stats = df.groupby(group_columns)['is_Anomaly'].agg(['mean', 'sum']).sort_values(by='mean', ascending=False).head(5)
    
    for label, row in group_stats.iterrows():
        # Handle single vs multiple group columns for clean printing
        group_label = label if isinstance(label, str) else " | ".join(map(str, label))
        rate_pct = row['mean'] * 100
        count = int(row['sum'])
        
        # Print the Rate % and the absolute count in brackets for context
        print(f" - {group_label:<25} : {rate_pct:>6.2f}% ({count:>3} anomalies)")
    # --- CHANGE END ---
    
    print("="*45 + "\n")

def calculate_ensemble_scores(df):
    """
    Calculates the normalized consensus score across all anomaly models.
    """
    
    # Identify all columns that are model flags (is_..._anomaly)
    anomaly_flags = [col for col in df.columns if col.startswith('is_') and col.endswith('_anomaly') and col != 'is_Anomaly']
    
    # 1. Total Votes (Count of True)
    df['Anomaly_Votes'] = df[anomaly_flags].sum(axis=1).astype(int)

        # 2. Total Models active for that row (Count of non-NaN values)
    df['Vote_Cnt'] = df[anomaly_flags].notna().sum(axis=1).astype(int)
    
    # 3. Raw Score (-1 to 1)
    # Calculation: 2 * (percentage_yes - 0.5)
    df['Anomaly_Score'] = 2 * (df['Anomaly_Votes'] / df['Vote_Cnt'] - 0.5)
    
    # 4. Display Score (-100 to 100)
    df['Anomaly_Score_Display'] = np.where(
        df['Anomaly_Score'] < 0, 
        np.floor(100 * df['Anomaly_Score']),
        np.where(df['Anomaly_Score'] > 0, np.ceil(100 * df['Anomaly_Score']), 0)
    ).astype(float)
    
    # 5. Final Boolean Consensus (e.g., majority rule)
    df['is_Anomaly'] = df['Anomaly_Votes'] / df['Vote_Cnt'] >= 0.5
    
    return df