"""
Auto-fixer - applies safe, deterministic fixes to data issues.
"""

from typing import Any

import pandas as pd
from dateutil.parser import parse as parse_date

from app.core.logging import get_logger
from app.autoclean.issue_detector import DataIssue, IssueType

logger = get_logger(__name__)


class FixResult:
    """Result of applying a fix."""
    
    def __init__(self, success: bool, message: str, details: dict[str, Any] = None):
        self.success = success
        self.message = message
        self.details = details or {}


class AutoFixer:
    """Applies safe, deterministic fixes to data issues."""
    
    def __init__(self, conservative: bool = True):
        """
        Initialize fixer.
        
        Args:
            conservative: If True, only apply very safe fixes
        """
        self.conservative = conservative
        self.logger = logger
    
    def fix_issue(self, df: pd.DataFrame, issue: DataIssue) -> tuple[pd.DataFrame, FixResult]:
        """
        Fix a specific data issue.
        
        Args:
            df: DataFrame to fix
            issue: Issue to fix
            
        Returns:
            Tuple of (fixed DataFrame, fix result)
        """
        fix_methods = {
            IssueType.MISSING_HEADERS: self._fix_missing_headers,
            IssueType.DUPLICATE_ROWS: self._fix_duplicates,
            IssueType.MIXED_DATE_FORMATS: self._fix_mixed_dates,
            IssueType.EXTRA_WHITESPACE: self._fix_whitespace,
            IssueType.INCONSISTENT_NULLS: self._fix_inconsistent_nulls,
            IssueType.MIXED_TYPES: self._fix_mixed_types,
        }
        
        fix_method = fix_methods.get(issue.issue_type)
        if not fix_method:
            return df, FixResult(
                False,
                f"No fix available for {issue.issue_type}",
            )
        
        try:
            return fix_method(df, issue)
        except Exception as e:
            self.logger.error(f"Fix failed: {e}", exc_info=True)
            return df, FixResult(
                False,
                f"Fix failed: {str(e)}",
            )
    
    def _fix_missing_headers(self, df: pd.DataFrame, issue: DataIssue) -> tuple[pd.DataFrame, FixResult]:
        """Fix missing headers by using first row."""
        # Use first row as headers
        new_headers = df.iloc[0].tolist()
        df_fixed = df[1:].copy()
        df_fixed.columns = new_headers
        df_fixed = df_fixed.reset_index(drop=True)
        
        return df_fixed, Fix Result(
            True,
            f"Added headers from first row: {', '.join(map(str, new_headers[:5]))}...",
            {"new_headers": new_headers},
        )
    
    def _fix_duplicates(self, df: pd.DataFrame, issue: DataIssue) -> tuple[pd.DataFrame, FixResult]:
        """Remove duplicate rows."""
        original_count = len(df)
        df_fixed = df.drop_duplicates(keep="first")
        removed_count = original_count - len(df_fixed)
        
        return df_fixed, FixResult(
            True,
            f"Removed {removed_count} duplicate rows",
            {"removed": removed_count},
        )
    
    def _fix_mixed_dates(self, df: pd.DataFrame, issue: DataIssue) -> tuple[pd.DataFrame, FixResult]:
        """Standardize date formats."""
        if not issue.column:
            return df, FixResult(False, "No column specified")
        
        df_fixed = df.copy()
        col = issue.column
        
        # Try to parse dates
        def safe_parse_date(val):
            if pd.isna(val):
                return None
            try:
                return parse_date(str(val)).strftime("%Y-%m-%d")
            except:
                return val  # Keep original if can't parse
        
        df_fixed[col] = df[col].apply(safe_parse_date)
        
        formats = issue.details.get("formats", [])
        return df_fixed, FixResult(
            True,
            f"Standardized dates in '{col}' to YYYY-MM-DD",
            {"original_formats": formats},
        )
    
    def _fix_whitespace(self, df: pd.DataFrame, issue: DataIssue) -> tuple[pd.DataFrame, FixResult]:
        """Remove extra whitespace."""
        if not issue.column:
            return df, FixResult(False, "No column specified")
        
        df_fixed = df.copy()
        col = issue.column
        
        # Strip whitespace
        df_fixed[col] = df[col].astype(str).str.strip()
        
        return df_fixed, FixResult(
            True,
            f"Removed extra whitespace from '{col}'",
        )
    
    def _fix_inconsistent_nulls(self, df: pd.DataFrame, issue: DataIssue) -> tuple[pd.DataFrame, FixResult]:
        """Convert string nulls to proper NaN."""
        if not issue.column:
            return df, FixResult(False, "No column specified")
        
        df_fixed = df.copy()
        col = issue.column
        
        null_patterns = ["null", "n/a", "na", "none", "nan", "", " "]
        
        # Replace with NaN
        df_fixed[col] = df[col].replace(null_patterns, pd.NA)
        df_fixed[col] = df_fixed[col].replace(r'^\s*$', pd.NA, regex=True)
        
        null_count = issue.details.get("null_count", 0)
        return df_fixed, FixResult(
            True,
            f"Standardized {null_count} null values in '{col}'",
            {"null_count": null_count},
        )
    
    def _fix_mixed_types(self, df: pd.DataFrame, issue: DataIssue) -> tuple[pd.DataFrame, FixResult]:
        """Handle mixed types conservatively."""
        if not issue.column:
            return df, FixResult(False, "No column specified")
        
        df_fixed = df.copy()
        col = issue.column
        
        if self.conservative:
            # Conservative: convert to string
            df_fixed[col] = df[col].astype(str)
            
            return df_fixed, FixResult(
                True,
                f"Converted '{col}' to string for safety (mixed types detected)",
                {"strategy": "conservative_to_string"},
            )
        else:
            # Try to coerce to numeric
            df_fixed[col] = pd.to_numeric(df[col], errors="coerce")
            
            return df_fixed, FixResult(
                True,
                f"Attempted numeric conversion for '{col}'",
                {"strategy": "coerce_numeric"},
            )
