import numpy as np
import pandas as pd
from procesing.pricers.base import PricingFunction


def session_features_to_demand(session_features: pd.DataFrame) -> float:
    """
    Map session behavioral features to demand proxy.
    THIS is the critical θ̂ → D transformation for rule-based pricing.

    Logic:
      - High velocity → agent behavior → price up (revenue recovery)
      - High cart ratio → purchase intent → price up
      - Low activity → discount to convert

    Returns: demand proxy score (0-20 range, higher = more demand)
    """
    if session_features.empty:
        return 1.0

    feat = session_features.iloc[0] if len(session_features) > 0 else {}

    velocity = feat.get('interaction_velocity', 0)
    cart_ratio = feat.get('cart_to_view_ratio', 0)
    item_views = feat.get('item_views', 0)
    cart_adds = feat.get('cart_adds', 0)

    # baseline demand
    demand = 1.0

    # agent detection: high velocity → treat as high "demand" to price up
    if velocity > 2.0:
        demand += 10.0  # strong agent signal

    # conversion intent: cart interaction → price up
    if cart_ratio > 0.1 or cart_adds > 0:
        demand += 5.0

    # browsing depth: many views → interest signal
    if item_views > 3:
        demand += min(item_views, 5.0)

    return min(demand, 20.0)  # cap at 20


class StaticPricer(PricingFunction):
    """Static pricing: always return fixed base prices"""

    def __init__(self, base_prices: np.ndarray = None):
        self.base_prices = base_prices

    def fit(self, historical_data: pd.DataFrame):
        """Extract base prices from historical data"""
        if 'base_price' in historical_data.columns:
            self.base_prices = historical_data['base_price'].values
        elif 'price' in historical_data.columns:
            self.base_prices = historical_data['price'].values
        else:
            raise ValueError("historical_data must contain 'base_price' or 'price' column")
        return self

    def predict(self, state_space) -> np.ndarray:
        """Return static base prices regardless of state"""
        if self.base_prices is None:
            raise ValueError("Must call fit() or provide base_prices in constructor")
        return self.base_prices.copy()

    def _get_features(self, state_space=None) -> np.ndarray:
        """Static pricer uses no features, returns empty array"""
        n = len(self.base_prices) if self.base_prices is not None else 0
        return np.zeros((n, 0))


class RandomPricer(PricingFunction):
    """Random pricing within bounds (for baseline comparison)"""

    def __init__(self, price_min: float = 50.0, price_max: float = 500.0, seed: int = None):
        self.price_min = price_min
        self.price_max = price_max
        self.seed = seed
        self.n_products = None
        self.rng = np.random.default_rng(seed)

    def fit(self, historical_data: pd.DataFrame):
        """Learn number of products"""
        self.n_products = len(historical_data)
        return self

    def predict(self, state_space) -> np.ndarray:
        """Generate random prices"""
        if self.n_products is None:
            self.n_products = len(state_space.demand)
        return self.rng.uniform(self.price_min, self.price_max, size=self.n_products)

    def _get_features(self, state_space=None) -> np.ndarray:
        """Random pricer uses no features"""
        n = self.n_products if self.n_products else 0
        return np.zeros((n, 0))


class SimpleSurgePricer(PricingFunction):
    """
    Rule-based surge pricer adjusting prices via demand thresholds.
    Logic: if demand > high_threshold -> surge, if demand < low_threshold -> discount.
    Simpler and more controllable than curve fitting approaches.
    """

    def __init__(self,
                 base_prices: np.ndarray = None,
                 high_threshold: int = 10,
                 low_threshold: int = 2,
                 surge_multiplier: float = 1.2,
                 discount_multiplier: float = 0.9):
        self.base_prices = base_prices
        self.high_threshold = high_threshold
        self.low_threshold = low_threshold
        self.surge_multiplier = surge_multiplier
        self.discount_multiplier = discount_multiplier

    def fit(self, market_data: pd.DataFrame):
        """Extract base prices from product catalog or historical averages"""
        self.base_prices = market_data['base_price'].to_numpy() if 'base_price' in market_data.columns else market_data['price'].values
        return self

    def predict(self, state_space) -> np.ndarray:
        """
        Adjust prices based on current demand using surge rules.
        state_space.demand: demand proxy per product (from session features)
        state_space.prices: base prices
        """
        demand = np.asarray(state_space.demand) if state_space and hasattr(state_space, 'demand') else np.array([0])
        base = np.asarray(state_space.prices) if state_space and hasattr(state_space, 'prices') else self.base_prices

        if base is None:
            base = np.ones(len(demand)) * 99.99

        # ensure float dtype to allow multiplication by float multipliers
        new_prices = base.astype(np.float64).copy()
        high_mask = demand >= self.high_threshold
        new_prices[high_mask] *= self.surge_multiplier

        low_mask = demand <= self.low_threshold
        new_prices[low_mask] *= self.discount_multiplier

        return new_prices

    def _get_features(self, state_space=None) -> np.ndarray:
        """Extract demand and base price features for each product"""
        if state_space is None:
            n = len(self.base_prices) if self.base_prices is not None else 0
            return np.zeros((n, 2))

        demand = np.asarray(state_space.demand) if hasattr(state_space, 'demand') else np.array([0])
        base = np.asarray(state_space.prices) if hasattr(state_space, 'prices') else self.base_prices
        if base is None:
            base = np.ones(len(demand)) * 99.99

        return np.column_stack([demand, base])
