"""
Structural Diff Reporter - Track exact structural changes.

Shows:
- Columns added/removed/realigned
- Types fixed
- Schema changes
- Rows truncated/padded
"""

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


@dataclass
class StructuralChange:
    """A single structural change."""
    change_type: str  # 'column_added', 'column_removed', 'type_changed', etc.
    description: str
    before: Any
    after: Any
    affected_items: List[str] = field(default_factory=list)


@dataclass
class StructuralDiffReport:
    """Complete structural diff report."""
    columns_added: List[str] = field(default_factory=list)
    columns_removed: List[str] = field(default_factory=list)
    columns_realigned: List[str] = field(default_factory=list)
    types_fixed: Dict[str, tuple] = field(default_factory=dict)  # column -> (before, after)
    schema_version_change: tuple = None  # (before, after)
    rows_truncated: int = 0
    rows_padded: int = 0
    rows_removed: int = 0
    rows_added: int = 0
    all_changes: List[StructuralChange] = field(default_factory=list)
    
    def summary(self) -> Dict[str, Any]:
        """Get summary dict."""
        return {
            'columns_added': self.columns_added,
            'columns_removed': self.columns_removed,
            'columns_realigned': self.columns_realigned,
            'types_fixed': {
                col: {'before': before, 'after': after}
                for col, (before, after) in self.types_fixed.items()
            },
            'schema_version': {
                'before': self.schema_version_change[0] if self.schema_version_change else None,
                'after': self.schema_version_change[1] if self.schema_version_change else None
            },
            'row_changes': {
                'truncated': self.rows_truncated,
                'padded': self.rows_padded,
                'removed': self.rows_removed,
                'added': self.rows_added
            },
            'total_changes': len(self.all_changes)
        }
    
    def to_json(self, indent: int = 2) -> str:
        """Export as JSON."""
        return json.dumps(self.summary(), indent=indent)


class StructuralDiffTracker:
    """Track structural changes during processing."""
    
    def __init__(self):
        self.report = StructuralDiffReport()
    
    def track_column_added(self, column_name: str):
        """Track a column addition."""
        self.report.columns_added.append(column_name)
        self.report.all_changes.append(StructuralChange(
            change_type='column_added',
            description=f"Added column '{column_name}'",
            before=None,
            after=column_name
        ))
    
    def track_column_removed(self, column_name: str):
        """Track a column removal."""
        self.report.columns_removed.append(column_name)
        self.report.all_changes.append(StructuralChange(
            change_type='column_removed',
            description=f"Removed column '{column_name}'",
            before=column_name,
            after=None
        ))
    
    def track_column_realigned(self, column_name: str, old_pos: int, new_pos: int):
        """Track a column reordering."""
        self.report.columns_realigned.append(column_name)
        self.report.all_changes.append(StructuralChange(
            change_type='column_realigned',
            description=f"Moved '{column_name}' from position {old_pos} to {new_pos}",
            before=old_pos,
            after=new_pos
        ))
    
    def track_type_change(self, column_name: str, old_type: str, new_type: str):
        """Track a type change."""
        self.report.types_fixed[column_name] = (old_type, new_type)
        self.report.all_changes.append(StructuralChange(
            change_type='type_changed',
            description=f"Changed '{column_name}' type from {old_type} to {new_type}",
            before=old_type,
            after=new_type
        ))
    
    def track_rows_truncated(self, count: int):
        """Track row truncation."""
        self.report.rows_truncated += count
        self.report.all_changes.append(StructuralChange(
            change_type='rows_truncated',
            description=f"Truncated {count} rows",
            before=None,
            after=count
        ))
    
    def track_rows_padded(self, count: int):
        """Track row padding."""
        self.report.rows_padded += count
        self.report.all_changes.append(StructuralChange(
            change_type='rows_padded',
            description=f"Padded {count} rows",
            before=None,
            after=count
        ))
    
    def track_schema_version_change(self, old_version: str, new_version: str):
        """Track schema version change."""
        self.report.schema_version_change = (old_version, new_version)
        self.report.all_changes.append(StructuralChange(
            change_type='schema_version_change',
            description=f"Schema version: {old_version} → {new_version}",
            before=old_version,
            after=new_version
        ))
    
    def compare_dataframes(
        self,
        df_before: pd.DataFrame,
        df_after: pd.DataFrame
    ) -> StructuralDiffReport:
        """
        Compare two DataFrames and generate diff.
        
        Args:
            df_before: Original DataFrame
            df_after: Modified DataFrame
        """
        # Column changes
        cols_before = set(df_before.columns)
        cols_after = set(df_after.columns)
        
        for col in cols_after - cols_before:
            self.track_column_added(col)
        
        for col in cols_before - cols_after:
            self.track_column_removed(col)
        
        # Column reordering
        common_cols = cols_before & cols_after
        for col in common_cols:
            old_pos = df_before.columns.tolist().index(col)
            new_pos = df_after.columns.tolist().index(col)
            if old_pos != new_pos:
                self.track_column_realigned(col, old_pos, new_pos)
        
        # Type changes
        for col in common_cols:
            old_type = str(df_before[col].dtype)
            new_type = str(df_after[col].dtype)
            if old_type != new_type:
                self.track_type_change(col, old_type, new_type)
        
        # Row count changes
        row_diff = len(df_after) - len(df_before)
        if row_diff > 0:
            self.report.rows_added = row_diff
        elif row_diff < 0:
            self.report.rows_removed = abs(row_diff)
        
        return self.report
    
    def get_report(self) -> StructuralDiffReport:
        """Get the current diff report."""
        return self.report
