
import pandas as pd
import numpy as np

class ScorecardTransformer:
    def __init__(self, pdo=20, base_score=600, base_odds=50):
        self.pdo = pdo
        self.base_score = base_score
        self.base_odds = base_odds
        self.factor = 0
        self.offset = 0
        self.scorecard = None
        
    def fit(self, model, binning_process, selected_features):
        """
        Generate scorecard points.
        """
        # Calculate Factor and Offset
        # Score = Offset - Factor * log(odds)  <-- Standard form for bad rate
        # If model predicts prob of bad (1):
        # log(odds) = log(p/1-p) = intercept + sum(coef * x)
        # We want higher score for lower risk (good).
        # Score = BaseScore + PDO * log2(Odds/BaseOdds) ?? No.
        
        # Standard:
        # Factor = pdo / ln(2)
        # Offset = base_score - factor * ln(base_odds)
        # Score = Offset + Factor * ln(odds_good)
        # Odds Good = P(Good)/P(Bad) = (1-p)/p = 1/odds_bad
        # ln(odds_good) = -ln(odds_bad)
        # ln(odds_bad) = intercept + sum(coef * woe) (assuming woe is for bads?)
        # Wait, my WOE calc was: log(dist_bad / dist_good). This is WOE_bad.
        # Higher WOE_bad => Higher Risk.
        # Log_odds_bad = Intercept + sum(coef * woe_bad)
        # Score = Offset - Factor * log_odds_bad
        # Score = Offset - Factor * (Intercept + sum(coef * woe))
        
        self.factor = self.pdo / np.log(2)
        self.offset = self.base_score + self.factor * np.log(self.base_odds)
        
        # Extract coefficients
        if hasattr(model, 'best_estimator_'):
            estimator = model.best_estimator_
        else:
            estimator = model
            
        intercept = estimator.intercept_[0]
        coefs = dict(zip(selected_features, estimator.coef_[0]))
        
        n_features = len(selected_features)
        
        # Base points distribution
        # We distribute (Offset - Factor * Intercept) across features
        # base_points_per_feature = (self.offset - self.factor * intercept) / n_features
        # Actually usually Intercept is part of the equation.
        # Score = (Offset - Factor * Intercept) + sum( -Factor * coef_i * woe_i )
        
        intercept_points = self.offset - self.factor * intercept
        
        card_data = []
        
        for feature in selected_features:
            rule = binning_process.rules[feature]
            coef = coefs[feature]
            
            # For each bin
            stats = rule['stats']
            
            # We add intercept_points/n_features to each bin
            # But usually we want a base score.
            # Let's stick to: Points = -Factor * (coef * woe + intercept/n) + Offset/n ?
            # Let's simplify: 
            # Score = A - B * log(odds)
            # A = Offset
            # B = Factor
            # log(odds) = alpha + beta*WOE
            # Score = A - B(alpha + beta*WOE) = (A - B*alpha) + (-B*beta)*WOE
            
            # Total Base Score (A - B*alpha)
            # We can just output this as "Base Score" row
            # And feature points as -B*beta*WOE
            
            # However, typical scorecard puts all points in bins.
            # So we distribute (A - B*alpha) equally.
            
            base_points = intercept_points / n_features
            
            for bin_stat in stats:
                woe = bin_stat['woe']
                points = base_points - self.factor * coef * woe
                
                card_data.append({
                    'Variable': feature,
                    'Bin': f"[{bin_stat['min']}, {bin_stat['max']})",
                    'WOE': woe,
                    'Points': round(points)
                })
                
        self.scorecard = pd.DataFrame(card_data)
        return self.scorecard
        
    def transform(self, X_woe, selected_features, model):
        """
        Calculate final score for dataset.
        """
        # We can use the formula directly:
        # Score = Offset - Factor * (Intercept + sum(Coef * WOE))
        
        if hasattr(model, 'best_estimator_'):
            estimator = model.best_estimator_
        else:
            estimator = model
            
        intercept = estimator.intercept_[0]
        coefs = estimator.coef_[0]
        
        # Align coefs with X_woe columns (assuming X_woe contains selected_features)
        # Note: X_woe might have more columns. We need to select relevant ones in order.
        
        X_sel = X_woe[selected_features].copy()
        
        # Handle NaN values (SGDClassifier doesn't accept NaN)
        X_sel = X_sel.fillna(0)
        
        # Log odds
        # log_odds = intercept + np.dot(X_sel, coefs) -> coefs need to match columns
        # estimator.decision_function(X_sel) does exactly this (X * coef + intercept)
        
        log_odds = estimator.decision_function(X_sel)
        
        scores = self.offset - self.factor * log_odds
        return scores
