"""Dashboard API endpoints — aggregated data for AJAX loading."""
from __future__ import annotations

from datetime import datetime

from flask import Blueprint, jsonify
from flask_login import login_required
from sqlalchemy import func

from app.extensions import db
from app.models.asset import Asset, AssetProfile
from app.models.ohlcv import OHLCVData
from app.models.signal import TradingSignal
from app.models.portfolio import Portfolio
from app.models.watchlist import Watchlist
from app.models.settings import AppSettings
from app.helpers.asset_filter import apply_asset_filter, get_active_data_source
from app.helpers.auth import get_current_user_id

api_dashboard_bp = Blueprint('api_dashboard', __name__)


def _get_combined_asset_ids() -> list[str]:
    """Get deduplicated asset IDs from watchlist + portfolio, filtered by asset_mode."""
    from app.helpers.asset_filter import get_asset_mode
    asset_mode = get_asset_mode()

    watchlist_ids = [w.asset_id for w in
                     db.session.query(Watchlist.asset_id)
                     .join(Asset, Watchlist.asset_id == Asset.id)
                     .filter(Watchlist.user_id == get_current_user_id(),
                             Asset.asset_type == asset_mode).all()]
    portfolio_ids = [p.asset_id for p in
                     db.session.query(Portfolio.asset_id)
                     .join(Asset, Portfolio.asset_id == Asset.id)
                     .filter(Portfolio.user_id == get_current_user_id(),
                             Asset.asset_type == asset_mode,
                             Portfolio.total_quantity > 0).all()]
    return list(set(watchlist_ids + portfolio_ids))


def _get_sentiment_label(value: float) -> str:
    """Map 0-100 sentiment value to label."""
    if value < 20:
        return 'Extreme Fear'
    elif value < 40:
        return 'Fear'
    elif value < 60:
        return 'Neutral'
    elif value < 80:
        return 'Greed'
    return 'Extreme Greed'


@api_dashboard_bp.route('/summary')
@login_required
def get_summary():
    """Aggregated dashboard data for AJAX loading.

    Returns market overview, sentiment index, sparklines, and scores
    for all watchlist + portfolio assets.
    """
    combined_ids = _get_combined_asset_ids()

    # --- Market Overview ---
    total_active = apply_asset_filter(Asset.query.filter_by(is_active=True)).count()
    total_tracked = apply_asset_filter(Asset.query).count()

    data_source = get_active_data_source()

    # BTC price (if bitcoin in combined set, or just get it anyway)
    btc_price_idr = None
    btc_change_24h = None
    btc_profile = AssetProfile.query.filter_by(
        asset_id='COIN.bitcoin'
    ).order_by(AssetProfile.fetched_at.desc()).first()
    if btc_profile:
        btc_price_idr = float(btc_profile.current_price_idr) if btc_profile.current_price_idr else None
        btc_change_24h = float(btc_profile.price_change_24h) if btc_profile.price_change_24h else None

    # Last sync time
    last_sync_row = db.session.query(
        func.max(OHLCVData.created_at)
    ).scalar()
    last_sync = last_sync_row.isoformat() if last_sync_row else None

    market_overview = {
        'total_coins_tracked': total_tracked,
        'total_active': total_active,
        'btc_price_idr': btc_price_idr,
        'btc_change_24h': btc_change_24h,
        'data_source': data_source,
        'last_sync': last_sync,
    }

    # --- Sentiment Index ---
    sentiment_index = {
        'value': 50.0,
        'label': 'Neutral',
        'buy_count': 0,
        'sell_count': 0,
        'hold_count': 0,
        'avg_score': 0.0,
    }

    if combined_ids:
        # Get most recent active signal per asset using subquery
        latest_signal_sq = db.session.query(
            TradingSignal.asset_id,
            func.max(TradingSignal.created_at).label('max_created')
        ).filter(
            TradingSignal.asset_id.in_(combined_ids),
            TradingSignal.status == 'active',
        ).group_by(TradingSignal.asset_id).subquery()

        signals_for_sentiment = TradingSignal.query.join(
            latest_signal_sq,
            db.and_(
                TradingSignal.asset_id == latest_signal_sq.c.asset_id,
                TradingSignal.created_at == latest_signal_sq.c.max_created,
            )
        ).filter(TradingSignal.status == 'active').all()

        if signals_for_sentiment:
            buy_count = sum(1 for s in signals_for_sentiment if s.signal_type == 'BUY')
            sell_count = sum(1 for s in signals_for_sentiment if s.signal_type == 'SELL')
            hold_count = sum(1 for s in signals_for_sentiment if s.signal_type == 'HOLD')
            avg_score = sum(float(s.score) for s in signals_for_sentiment) / len(signals_for_sentiment)

            # Normalize: score range -100..+100 → sentiment 0..100
            sentiment_value = (avg_score + 100) / 2
            sentiment_value = max(0, min(100, sentiment_value))

            sentiment_index = {
                'value': round(sentiment_value, 1),
                'label': _get_sentiment_label(sentiment_value),
                'buy_count': buy_count,
                'sell_count': sell_count,
                'hold_count': hold_count,
                'avg_score': round(avg_score, 1),
            }

    # --- Sparklines (7-day daily closes) ---
    sparklines = {}
    if combined_ids:
        source = data_source if isinstance(data_source, str) else 'coingecko'
        for asset_id in combined_ids:
            records = OHLCVData.query.filter_by(
                asset_id=asset_id, timeframe='1D', source=source,
            ).order_by(OHLCVData.datetime_wib.desc()).limit(7).all()
            records.reverse()
            if records:
                sparklines[asset_id] = [
                    {'time': int(r.timestamp), 'close': float(r.close)}
                    for r in records
                ]

    # --- Scores (most recent active signal per asset) ---
    scores = {}
    if combined_ids:
        # Reuse signals_for_sentiment if available
        signal_list = signals_for_sentiment if 'signals_for_sentiment' in dir() else []
        if not signal_list:
            # Fallback: query again
            latest_sq = db.session.query(
                TradingSignal.asset_id,
                func.max(TradingSignal.created_at).label('max_c')
            ).filter(
                TradingSignal.asset_id.in_(combined_ids),
                TradingSignal.status == 'active',
            ).group_by(TradingSignal.asset_id).subquery()

            signal_list = TradingSignal.query.join(
                latest_sq,
                db.and_(
                    TradingSignal.asset_id == latest_sq.c.asset_id,
                    TradingSignal.created_at == latest_sq.c.max_c,
                )
            ).filter(TradingSignal.status == 'active').all()

        for s in signal_list:
            scores[s.asset_id] = {
                'signal': s.signal_type,
                'score': float(s.score),
                'confidence': s.confidence,
                'safety_rating': s.safety_rating,
                'strategy': s.recommended_strategy,
                'last_signal_time': s.created_at.isoformat() if s.created_at else None,
            }

    return jsonify({
        'market_overview': market_overview,
        'sentiment_index': sentiment_index,
        'sparklines': sparklines,
        'scores': scores,
    })


# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
#  TRADING COMMAND CENTER — comprehensive dashboard data in a single API call
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

@api_dashboard_bp.route('/wealth-overview')
@login_required
def get_wealth_overview():
    """Comprehensive dashboard data — single API call for the trading command center.

    Returns wealth snapshot, market pulse, top opportunities, portfolio positions,
    active signals, recent trades, and system stats.
    """
    import math
    import logging
    from sqlalchemy import func as sa_func
    from app.models.user import User
    from app.models.admin import Admin
    from app.models.asset import Asset, AssetProfile
    from app.models.ohlcv import OHLCVData
    from app.models.portfolio import Portfolio, TradeHistory
    from app.models.signal import TradingSignal
    from app.helpers.asset_filter import get_asset_mode, get_active_data_source
    from app.helpers.auth import get_current_user_id

    log = logging.getLogger(__name__)

    def _sf(val, default=0.0):
        """Safe float — handles None, NaN, Inf."""
        if val is None:
            return default
        try:
            f = float(val)
            return default if (math.isnan(f) or math.isinf(f)) else f
        except (ValueError, TypeError):
            return default

    asset_mode = get_asset_mode()
    data_source = get_active_data_source()
    user_id = get_current_user_id()  # None for Admin → aggregate view

    # ══════════════════════════════════════════════════════════════════════
    #  A. WEALTH SNAPSHOT — portfolio value, P&L, win rate
    # ══════════════════════════════════════════════════════════════════════
    portfolio_query = Portfolio.query.join(
        Asset, Portfolio.asset_id == Asset.id
    ).filter(Portfolio.total_quantity > 0, Asset.asset_type == asset_mode)
    if user_id:
        portfolio_query = portfolio_query.filter(Portfolio.user_id == user_id)
    positions = portfolio_query.all()

    # Batch load latest AssetProfile per asset
    position_asset_ids = list({p.asset_id for p in positions})
    profile_map = {}
    if position_asset_ids:
        latest_sq = db.session.query(
            AssetProfile.asset_id,
            sa_func.max(AssetProfile.fetched_at).label('max_fetched')
        ).filter(
            AssetProfile.asset_id.in_(position_asset_ids)
        ).group_by(AssetProfile.asset_id).subquery()

        profiles = AssetProfile.query.join(
            latest_sq,
            db.and_(
                AssetProfile.asset_id == latest_sq.c.asset_id,
                AssetProfile.fetched_at == latest_sq.c.max_fetched,
            )
        ).all()
        profile_map = {p.asset_id: p for p in profiles}

    # Batch load Asset objects for names/symbols
    coin_map = {}
    if position_asset_ids:
        coins_list = Asset.query.filter(Asset.id.in_(position_asset_ids)).all()
        coin_map = {c.id: c for c in coins_list}

    total_portfolio_value = 0.0
    total_invested = 0.0
    total_realized = 0.0
    today_pnl = 0.0
    portfolio_items = []

    for pos in positions:
        prof = profile_map.get(pos.asset_id)
        asset = coin_map.get(pos.asset_id)
        qty = _sf(pos.total_quantity)
        avg_price = _sf(pos.avg_buy_price)
        invested = _sf(pos.total_invested_idr)
        realized = _sf(pos.realized_pnl)
        current_price = _sf(prof.current_price_idr) if prof else 0.0
        change_24h = _sf(prof.price_change_24h) if prof else 0.0

        market_value = qty * current_price
        unrealized = market_value - invested
        unrealized_pct = (unrealized / invested * 100) if invested > 0 else 0.0
        day_pnl = market_value * (change_24h / 100) if change_24h else 0.0

        total_portfolio_value += market_value
        total_invested += invested
        total_realized += realized
        today_pnl += day_pnl

        portfolio_items.append({
            'asset_id': pos.asset_id,
            'symbol': asset.symbol if asset else pos.asset_id,
            'name': asset.name if asset else pos.asset_id,
            'icon_thumb_url': asset.icon_thumb_url if asset else None,
            'total_quantity': round(qty, 8),
            'avg_buy_price': round(avg_price, 2),
            'current_price_idr': round(current_price, 2),
            'market_value_idr': round(market_value, 2),
            'invested_idr': round(invested, 2),
            'unrealized_pnl_idr': round(unrealized, 2),
            'unrealized_pnl_pct': round(unrealized_pct, 2),
            'price_change_24h': round(change_24h, 2),
            'sparkline_7d': [],  # filled below
        })

    # Sort positions by abs(unrealized_pnl)
    portfolio_items.sort(key=lambda x: abs(x['unrealized_pnl_idr']), reverse=True)

    # Sparklines for portfolio assets
    if position_asset_ids:
        source_str = data_source if isinstance(data_source, str) else 'coingecko'
        for item in portfolio_items:
            records = OHLCVData.query.filter_by(
                asset_id=item['asset_id'], timeframe='1D', source=source_str,
            ).order_by(OHLCVData.datetime_wib.desc()).limit(7).all()
            records.reverse()
            if records:
                item['sparkline_7d'] = [round(_sf(r.close), 2) for r in records]

    total_unrealized = total_portfolio_value - total_invested
    unrealized_pct = (total_unrealized / total_invested * 100) if total_invested > 0 else 0.0
    today_pnl_pct = (today_pnl / total_portfolio_value * 100) if total_portfolio_value > 0 else 0.0

    # Win rate from SELL trades
    trade_base = TradeHistory.query.join(Asset, TradeHistory.asset_id == Asset.id).filter(
        Asset.asset_type == asset_mode, TradeHistory.side == 'SELL'
    )
    if user_id:
        trade_base = trade_base.filter(TradeHistory.user_id == user_id)
    sell_trades = trade_base.all()
    total_sells = len(sell_trades)
    profitable_sells = sum(1 for t in sell_trades if t.pnl_idr and _sf(t.pnl_idr) > 0)
    win_rate = (profitable_sells / total_sells * 100) if total_sells > 0 else 0.0

    wealth_snapshot = {
        'total_portfolio_value_idr': round(total_portfolio_value, 2),
        'total_invested_idr': round(total_invested, 2),
        'unrealized_pnl_idr': round(total_unrealized, 2),
        'unrealized_pnl_pct': round(unrealized_pct, 2),
        'realized_pnl_idr': round(total_realized, 2),
        'today_pnl_idr': round(today_pnl, 2),
        'today_pnl_pct': round(today_pnl_pct, 2),
        'win_rate': round(win_rate, 1),
        'total_positions': len(positions),
        'total_closed_trades': total_sells,
    }

    # ══════════════════════════════════════════════════════════════════════
    #  B. MARKET PULSE — sentiment, signal counts, BTC price
    # ══════════════════════════════════════════════════════════════════════
    # Get ALL active signals (not just watchlist/portfolio)
    latest_sig_sq = db.session.query(
        TradingSignal.asset_id,
        sa_func.max(TradingSignal.created_at).label('max_created')
    ).join(Asset, TradingSignal.asset_id == Asset.id).filter(
        TradingSignal.status == 'active',
        Asset.asset_type == asset_mode,
        Asset.is_active.is_(True),
    ).group_by(TradingSignal.asset_id).subquery()

    all_signals = TradingSignal.query.join(
        latest_sig_sq,
        db.and_(
            TradingSignal.asset_id == latest_sig_sq.c.asset_id,
            TradingSignal.created_at == latest_sig_sq.c.max_created,
        )
    ).filter(TradingSignal.status == 'active').all()

    buy_count = sum(1 for s in all_signals if s.signal_type == 'BUY')
    sell_count = sum(1 for s in all_signals if s.signal_type == 'SELL')
    hold_count = sum(1 for s in all_signals if s.signal_type == 'HOLD')

    sentiment_value = 50.0
    sentiment_label = 'Neutral'
    if all_signals:
        avg_score = sum(_sf(s.score) for s in all_signals) / len(all_signals)
        sentiment_value = max(0, min(100, (avg_score + 100) / 2))
        sentiment_label = _get_sentiment_label(sentiment_value)

    # BTC price
    btc_price = None
    btc_change = None
    btc_prof = AssetProfile.query.filter_by(
        asset_id='COIN.bitcoin'
    ).order_by(AssetProfile.fetched_at.desc()).first()
    if btc_prof:
        btc_price = _sf(btc_prof.current_price_idr) or None
        btc_change = _sf(btc_prof.price_change_24h) or None

    last_sync_dt = db.session.query(sa_func.max(OHLCVData.created_at)).scalar()
    total_coins_tracked = Asset.query.filter(Asset.asset_type == asset_mode).count()
    total_active_coins = Asset.query.filter(
        Asset.asset_type == asset_mode, Asset.is_active.is_(True)
    ).count()

    market_pulse = {
        'sentiment_value': round(sentiment_value, 1),
        'sentiment_label': sentiment_label,
        'buy_signals_count': buy_count,
        'sell_signals_count': sell_count,
        'hold_signals_count': hold_count,
        'btc_price_idr': btc_price,
        'btc_change_24h': btc_change,
        'total_coins_tracked': total_coins_tracked,
        'total_active': total_active_coins,
        'data_source': data_source,
        'last_sync': last_sync_dt.isoformat() if last_sync_dt else None,
    }

    # ══════════════════════════════════════════════════════════════════════
    #  C. TOP OPPORTUNITIES — Buy Recommendations + Multibagger
    # ══════════════════════════════════════════════════════════════════════
    top_buys = []
    try:
        from app.services.buy_recommender import BuyRecommender
        buy_result = BuyRecommender().get_recommendations(limit=5, sort_by='score')
        for r in buy_result.get('items', []):
            daily_ev = 0
            proj = r.get('projections') or {}
            if proj.get('daily'):
                daily_ev = proj['daily'].get('expected_profit_idr', 0) or 0
            top_buys.append({
                'asset_id': r['asset_id'],
                'symbol': r['symbol'],
                'name': r['name'],
                'icon_thumb_url': r.get('icon_thumb_url'),
                'buy_score': round(_sf(r.get('buy_score')), 1),
                'success_rate_pct': round(_sf(r.get('success_rate_pct')), 1),
                'ev_per_cycle_pct': round(_sf(r.get('ev_per_cycle_pct')), 2),
                'cycles_per_day': round(_sf(r.get('cycles_per_day')), 2),
                'safety': r.get('safety', 'MODERATE'),
                'current_price_idr': _sf(r.get('current_price_idr')),
                'daily_ev_idr': round(_sf(daily_ev), 2),
            })
    except Exception as e:
        log.warning('BuyRecommender failed: %s', e)

    top_multibagger = []
    try:
        from app.services.multibagger_screener import MultibaggerScreener
        mb_result = MultibaggerScreener().get_candidates(limit=5, asset_type=asset_mode)
        for r in mb_result.get('items', []):
            top_multibagger.append({
                'asset_id': r['asset_id'],
                'symbol': r['symbol'],
                'name': r['name'],
                'icon_thumb_url': r.get('icon_thumb_url'),
                'multibagger_score': round(_sf(r.get('score')), 1),
                'estimated_multiple': round(_sf(r.get('estimated_multiple'), 1.0), 1),
                'tier': r.get('tier', ''),
                'current_price_idr': _sf(r.get('current_price_idr')),
                'price_change_24h': _sf(r.get('price_change_24h')),
                'ath_recovery_pct': _sf(r.get('ath_recovery_pct')),
            })
    except Exception as e:
        log.warning('MultibaggerScreener failed: %s', e)

    # ══════════════════════════════════════════════════════════════════════
    #  E. ACTIVE SIGNALS — top 20 by score
    # ══════════════════════════════════════════════════════════════════════
    # Build signal asset map for names
    signal_asset_ids = list({s.asset_id for s in all_signals})
    sig_coin_map = {}
    if signal_asset_ids:
        sig_coins = Asset.query.filter(Asset.id.in_(signal_asset_ids)).all()
        sig_coin_map = {c.id: c for c in sig_coins}

    sorted_signals = sorted(all_signals, key=lambda s: _sf(s.score), reverse=True)[:20]
    active_signals_list = []
    for s in sorted_signals:
        sc = sig_coin_map.get(s.asset_id)
        active_signals_list.append({
            'asset_id': s.asset_id,
            'symbol': sc.symbol if sc else s.asset_id,
            'name': sc.name if sc else s.asset_id,
            'icon_thumb_url': sc.icon_thumb_url if sc else None,
            'signal_type': s.signal_type,
            'confidence': s.confidence,  # ENUM string: High/Medium/Low
            'score': round(_sf(s.score), 1),
            'entry_price': _sf(s.entry_price),
            'stop_loss': _sf(s.stop_loss),
            'take_profit_1': _sf(s.take_profit_1),
            'safety_rating': s.safety_rating or 'MODERATE',
            'strategy': s.recommended_strategy,
            'created_at': s.created_at.isoformat() if s.created_at else None,
        })

    # ══════════════════════════════════════════════════════════════════════
    #  F. RECENT TRADES — last 10
    # ══════════════════════════════════════════════════════════════════════
    recent_q = TradeHistory.query.join(
        Asset, TradeHistory.asset_id == Asset.id
    ).filter(Asset.asset_type == asset_mode)
    if user_id:
        recent_q = recent_q.filter(TradeHistory.user_id == user_id)
    recent_raw = recent_q.order_by(TradeHistory.executed_at.desc()).limit(10).all()

    # Load asset names for trades
    trade_asset_ids = list({t.asset_id for t in recent_raw})
    trade_coin_map = {}
    if trade_asset_ids:
        tc = Asset.query.filter(Asset.id.in_(trade_asset_ids)).all()
        trade_coin_map = {c.id: c for c in tc}

    recent_trades = []
    trades_total_pnl = 0.0
    for t in recent_raw:
        tc = trade_coin_map.get(t.asset_id)
        pnl = _sf(t.pnl_idr)
        trades_total_pnl += pnl
        recent_trades.append({
            'asset_id': t.asset_id,
            'symbol': tc.symbol if tc else t.asset_id,
            'name': tc.name if tc else t.asset_id,
            'icon_thumb_url': tc.icon_thumb_url if tc else None,
            'side': t.side,
            'price': _sf(t.price),
            'quantity': round(_sf(t.quantity), 8),
            'total_value_idr': round(_sf(t.total_value_idr), 2),
            'fee_idr': round(_sf(t.fee_idr), 2),
            'pnl_idr': round(pnl, 2),
            'executed_at': t.executed_at.isoformat() if t.executed_at else None,
        })

    # ══════════════════════════════════════════════════════════════════════
    #  G. SYSTEM STATS
    # ══════════════════════════════════════════════════════════════════════
    system_stats = {
        'total_users': User.query.count(),
        'pending_users': User.query.filter_by(is_approved=False, is_active=True).count(),
        'active_users': User.query.filter_by(is_approved=True, is_active=True).count(),
        'total_admins': Admin.query.count(),
        'total_coins': total_active_coins,
    }

    return jsonify({
        'wealth_snapshot': wealth_snapshot,
        'market_pulse': market_pulse,
        'top_buy_recommendations': top_buys,
        'top_multibagger_candidates': top_multibagger,
        'portfolio_positions': portfolio_items,
        'active_signals': active_signals_list,
        'recent_trades': recent_trades,
        'recent_trades_total_pnl': round(trades_total_pnl, 2),
        'system_stats': system_stats,
    })
