"""Asset Multiplier - Algoritma Ninja #3.

Strategi akumulasi unit melalui Buy Low - Sell High di sideways range.
Adapted from kriptodownloader/app/quant_core/coin_multiplier.py
"""
from __future__ import annotations
import numpy as np
from app.engine.base import BaseAlgorithm
from app.engine.context import AlgorithmContext


class CoinMultiplierAlgorithm(BaseAlgorithm):
    algorithm_id = 'quantitative.coin_multiplier'
    version = '1.0.0'
    category = 'quantitative'
    display_name = 'Asset Multiplier'
    dependencies = ['technical.atr']

    def __init__(self, lookback: int = 20, range_threshold: float = 5.0,
                 volatility_threshold: float = 3.0, slope_threshold: float = 0.5):
        self.lookback = lookback
        self.range_threshold = range_threshold
        self.volatility_threshold = volatility_threshold
        self.slope_threshold = slope_threshold

    def _detect_sideways(self, closes, highs, lows):
        """Detect sideways market condition."""
        n = min(self.lookback, len(closes))
        recent_c = closes[-n:]
        recent_h = highs[-n:]
        recent_l = lows[-n:]

        h_avg = float(np.mean(recent_h))
        l_avg = float(np.mean(recent_l))
        c_avg = float(np.mean(recent_c))

        range_pct = (h_avg - l_avg) / c_avg * 100 if c_avg else 0
        volatility_pct = float(np.std(recent_c)) / c_avg * 100 if c_avg else 0

        x = np.arange(n)
        slope = float(np.polyfit(x, recent_c, 1)[0])
        slope_pct = slope / c_avg * 100 if c_avg else 0

        is_sideways = (
            range_pct < self.range_threshold and
            volatility_pct < self.volatility_threshold and
            abs(slope_pct) < self.slope_threshold
        )

        quality = (
            max(0, 100 - range_pct / self.range_threshold * 100) +
            max(0, 100 - volatility_pct / self.volatility_threshold * 100) +
            max(0, 100 - abs(slope_pct) / self.slope_threshold * 100)
        ) / 3

        return {
            'is_sideways': bool(is_sideways),
            'quality_score': float(quality),
            'support': float(l_avg),
            'resistance': float(h_avg),
            'midpoint': float(c_avg),
            'range_pct': float(range_pct),
            'volatility_pct': float(volatility_pct),
            'slope_pct': float(slope_pct),
        }

    def _generate_ladder(self, price, support, resistance, capital, num_levels=3):
        """Generate buy/sell ladder orders."""
        buy_prices = np.linspace(support, price * 0.98, num_levels) if price > support else [support]
        sell_prices = np.linspace(price * 1.02, resistance, num_levels) if resistance > price else [resistance]
        cash_per_level = capital * 0.3

        buy_orders = []
        for i, p in enumerate(buy_prices):
            qty = cash_per_level / p
            buy_orders.append({
                'level': i + 1, 'type': 'BUY',
                'price': float(p), 'quantity': float(qty),
                'value': float(cash_per_level),
            })

        avg_buy = float(np.mean(buy_prices))
        total_coins = (cash_per_level * num_levels) / avg_buy if avg_buy else 0
        coins_per_level = total_coins / num_levels

        sell_orders = []
        for i, p in enumerate(sell_prices):
            sell_orders.append({
                'level': i + 1, 'type': 'SELL',
                'price': float(p), 'quantity': float(coins_per_level),
                'value': float(coins_per_level * p),
            })

        avg_sell = float(np.mean(sell_prices))
        profit_per_cycle = (avg_sell - avg_buy) / avg_buy * 100 if avg_buy else 0

        return {
            'buy_orders': buy_orders,
            'sell_orders': sell_orders,
            'avg_buy_price': avg_buy,
            'avg_sell_price': avg_sell,
            'profit_per_cycle_pct': float(profit_per_cycle),
        }

    def _simulate_cycles(self, initial_coins, initial_cash, avg_buy, avg_sell, cycles=10):
        """Simulate unit accumulation over multiple cycles."""
        assets = initial_coins
        cash = initial_cash
        results = []

        for c in range(1, cycles + 1):
            cash_from_sell = assets * avg_sell
            total_cash = cash + cash_from_sell
            new_coins = total_cash / avg_buy if avg_buy else assets
            growth = (new_coins / max(initial_coins, 0.0001) - 1) * 100

            results.append({
                'cycle': c,
                'coins_before': float(assets),
                'coins_after': float(new_coins),
                'growth_pct': float(growth),
            })
            assets = new_coins
            cash = 0

        return {
            'cycles': results,
            'final_coins': float(assets),
            'multiplier': float(assets / max(initial_coins, 0.0001)),
        }

    def compute(self, ctx: AlgorithmContext) -> AlgorithmContext:
        df = ctx.ohlcv
        if df is None or len(df) < self.lookback:
            return ctx

        closes = df['close'].values.astype(float)
        highs = df['high'].values.astype(float)
        lows = df['low'].values.astype(float)

        sideways = self._detect_sideways(closes, highs, lows)
        result = {'sideways': sideways, 'orders': None, 'simulation': None}

        if sideways['is_sideways']:
            capital = ctx.capital_idr * (ctx.active_pct / 100)
            orders = self._generate_ladder(
                ctx.current_price, sideways['support'],
                sideways['resistance'], capital,
            )
            initial_coins = capital / ctx.current_price if ctx.current_price else 0
            simulation = self._simulate_cycles(
                initial_coins, capital,
                orders['avg_buy_price'], orders['avg_sell_price'],
            )
            result['orders'] = orders
            result['simulation'] = simulation

        ctx.quantitative['coin_multiplier'] = result
        return ctx

    def get_signal_contribution(self, ctx: AlgorithmContext) -> dict | None:
        cm = ctx.quantitative.get('coin_multiplier')
        if not cm:
            return None
        sw = cm['sideways']
        if sw['is_sideways'] and sw['quality_score'] > 60:
            return {'signal': 'BUY', 'weight': 0.08,
                    'reason': f"Sideways detected (quality {sw['quality_score']:.0f}), "
                              f"Asset Multiplier strategy viable"}
        return None
