"""Analysis API endpoints."""
import json
import logging
import traceback

from flask import Blueprint, jsonify, request, Response
from flask_login import login_required

api_analysis_bp = Blueprint('api_analysis', __name__)
log = logging.getLogger(__name__)

_engine = None
_scorer = None


class _SafeEncoder(json.JSONEncoder):
    """JSON encoder that handles numpy/pandas types gracefully."""

    def default(self, obj):
        try:
            import numpy as np
            if isinstance(obj, (np.integer,)):
                return int(obj)
            if isinstance(obj, (np.floating,)):
                return float(obj)
            if isinstance(obj, np.ndarray):
                return obj.tolist()
            if isinstance(obj, np.bool_):
                return bool(obj)
        except ImportError:
            pass
        try:
            import pandas as pd
            if isinstance(obj, pd.Timestamp):
                return obj.isoformat()
            if isinstance(obj, pd.Series):
                return obj.tolist()
        except ImportError:
            pass
        if hasattr(obj, '__float__'):
            return float(obj)
        if hasattr(obj, '__int__'):
            return int(obj)
        return str(obj)


def _safe_jsonify(data):
    """jsonify that handles numpy/pandas types."""
    payload = json.dumps(data, cls=_SafeEncoder, ensure_ascii=False)
    return Response(payload, content_type='application/json')


def _get_engine():
    global _engine
    if _engine is None:
        from app.engine import AlgorithmEngine
        _engine = AlgorithmEngine()
    return _engine


def _get_scorer():
    global _scorer
    if _scorer is None:
        from app.services.scorer import ScoringService
        _scorer = ScoringService(_get_engine())
    return _scorer


@api_analysis_bp.route('/run/<asset_id>')
@login_required
def run_analysis(asset_id):
    """Run full analysis pipeline on a asset."""
    from app.helpers.ml_mode import is_view_only

    # View-only: return latest cached TradingSignal instead of live compute
    if is_view_only():
        return _cached_analysis_response(asset_id)

    timeframe = request.args.get('timeframe', '1h')
    source = request.args.get('source')

    try:
        engine = _get_engine()
        ctx = engine.analyze(asset_id, timeframe, source)
        return _safe_jsonify(ctx.to_dict())
    except Exception as e:
        log.error('Analysis run error for %s: %s\n%s', asset_id, e, traceback.format_exc())
        return jsonify({
            'error': str(e),
            'asset_id': asset_id,
            'timeframe': timeframe,
            'indicators': {},
            'patterns': {},
            'quantitative': {},
            'ml_predictions': {},
            'strategy': {},
            'signal_contributions': {},
            'algorithm_versions': {},
            'errors': [{'algorithm': 'engine', 'error': str(e)}],
            'current_price': 0,
        }), 200  # Return 200 with error info so frontend can display it


@api_analysis_bp.route('/algorithms')
@login_required
def list_algorithms():
    """List all registered algorithms."""
    engine = _get_engine()
    return jsonify({
        'algorithms': engine.list_algorithms(),
        'execution_order': engine.get_execution_order(),
    })


@api_analysis_bp.route('/score/<asset_id>')
@login_required
def get_score(asset_id):
    """Get comprehensive score with ML predictions and strategy recommendation."""
    from app.helpers.ml_mode import is_view_only

    # View-only: return cached signal data
    if is_view_only():
        return _cached_score_response(asset_id)

    timeframe = request.args.get('timeframe', '1h')
    source = request.args.get('source')

    try:
        scorer = _get_scorer()
        report = scorer.score_coin(asset_id, timeframe, source)
        return _safe_jsonify(report)
    except Exception as e:
        log.error('Score error for %s: %s\n%s', asset_id, e, traceback.format_exc())
        return jsonify({'error': str(e)}), 500


@api_analysis_bp.route('/score/<asset_id>/save', methods=['POST'])
@login_required
def save_signal(asset_id):
    """Generate and save a trading signal to the database."""
    from app.helpers.ml_mode import is_view_only

    if is_view_only():
        return jsonify({
            'error': 'ML mode is view_only. Live computation disabled.',
            'ml_mode': 'view_only',
        }), 403
    timeframe = request.args.get('timeframe', '1h')
    source = request.args.get('source')

    try:
        scorer = _get_scorer()
        report = scorer.score_coin(asset_id, timeframe, source)
        signal_id = scorer.save_signal(report)

        return jsonify({
            'signal_id': signal_id,
            'signal': report['signal'],
            'score': report['score'],
            'confidence': report['confidence'],
        })
    except Exception as e:
        log.error('Save signal error for %s: %s\n%s', asset_id, e, traceback.format_exc())
        return jsonify({'error': str(e)}), 500


@api_analysis_bp.route('/signals')
@login_required
def list_signals():
    """List recent trading signals."""
    from app.models.signal import TradingSignal
    from app.models.asset import Asset
    from app.helpers.asset_filter import get_asset_mode

    limit = request.args.get('limit', 20, type=int)
    asset_id = request.args.get('asset_id')

    query = TradingSignal.query.join(Asset, TradingSignal.asset_id == Asset.id)\
        .filter(Asset.asset_type == get_asset_mode())
    if asset_id:
        query = query.filter(TradingSignal.asset_id == asset_id)
    query = query.order_by(TradingSignal.created_at.desc()).limit(limit)

    signals = []
    for s in query.all():
        signals.append({
            'id': s.id,
            'asset_id': s.asset_id,
            'signal_type': s.signal_type,
            'confidence': s.confidence,
            'score': float(s.score),
            'recommended_strategy': s.recommended_strategy,
            'safety_rating': s.safety_rating,
            'entry_price': float(s.entry_price) if s.entry_price else None,
            'stop_loss': float(s.stop_loss) if s.stop_loss else None,
            'take_profit_1': float(s.take_profit_1) if s.take_profit_1 else None,
            'status': s.status,
            'created_at': s.created_at.isoformat() if s.created_at else None,
        })

    return jsonify({'signals': signals})


# ── Cached response helpers (view_only mode) ─────────────────────────

def _cached_analysis_response(asset_id):
    """Return latest cached TradingSignal as analysis-compatible response.

    Supports two storage formats in ``indicators_json``:
    - **New format** (nested dict): ``{contributions, indicators, patterns, ...}``
    - **Legacy format** (flat dict): direct signal contributions per algorithm
    """
    from app.models.signal import TradingSignal
    from app.models.asset import AssetProfile

    timeframe = request.args.get('timeframe', '1D')

    # Get current price from latest profile
    profile = AssetProfile.query.filter_by(asset_id=asset_id)\
        .order_by(AssetProfile.fetched_at.desc()).first()
    fallback_price = float(profile.current_price_idr or 0) if profile else 0

    # Try to find signal matching timeframe first, then fallback to any
    sig = TradingSignal.query.filter_by(
        asset_id=asset_id, timeframe=timeframe, status='active',
    ).order_by(TradingSignal.created_at.desc()).first()

    if not sig:
        # Fallback: any active signal for this asset
        sig = TradingSignal.query.filter_by(
            asset_id=asset_id, status='active',
        ).order_by(TradingSignal.created_at.desc()).first()

    if not sig:
        # Last resort: any signal regardless of status
        sig = TradingSignal.query.filter_by(asset_id=asset_id)\
            .order_by(TradingSignal.created_at.desc()).first()

    if not sig:
        return jsonify({
            'asset_id': asset_id,
            'ml_mode': 'view_only',
            'cached': True,
            'message': 'Belum ada data analisis. Jalankan bulk scan dari Admin ML.',
            'signal': 'HOLD',
            'score': 0,
            'confidence': 'Low',
            'indicators': {},
            'patterns': {},
            'quantitative': {},
            'ml_predictions': {},
            'strategy': {},
            'signal_contributions': {},
            'algorithm_versions': {},
            'errors': [],
            'current_price': fallback_price,
        })

    stored = sig.indicators_json or {}

    # Detect format: new format has 'contributions' key
    if 'contributions' in stored:
        # New format — full context stored
        contributions = stored.get('contributions', {})
        indicators = stored.get('indicators', {})
        patterns = stored.get('patterns', {})
        quantitative = stored.get('quantitative', {})
        ml_predictions = stored.get('ml_predictions', {})
        strategy_full = stored.get('strategy', {})
        velocity = stored.get('velocity', {})
        money_mgmt = stored.get('money_mgmt', {})
        current_price = stored.get('current_price', fallback_price)
    else:
        # Legacy format — stored is flat contributions dict
        contributions = {}
        for aid, data in stored.items():
            if isinstance(data, dict) and 'signal' in data:
                contributions[aid] = {
                    'signal': data.get('signal', 'HOLD'),
                    'weight': data.get('weight', 0),
                    'reason': data.get('reason', ''),
                }
        indicators = {}
        patterns = {}
        quantitative = {}
        ml_predictions = {}
        strategy_full = {}
        velocity = {}
        money_mgmt = {}
        current_price = fallback_price

    # Ensure strategy.selector exists for template rendering
    if not strategy_full.get('selector') and sig.recommended_strategy:
        strategy_full['selector'] = {
            'recommended': sig.recommended_strategy,
            'safety_rating': sig.safety_rating,
        }

    return _safe_jsonify({
        'asset_id': asset_id,
        'timeframe': sig.timeframe or timeframe,
        'ml_mode': 'view_only',
        'cached': True,
        'cached_at': sig.created_at.isoformat() if sig.created_at else None,
        'signal': sig.signal_type,
        'score': float(sig.score),
        'confidence': sig.confidence,
        'recommended_strategy': sig.recommended_strategy,
        'safety_rating': sig.safety_rating,
        'entry_price': float(sig.entry_price) if sig.entry_price else None,
        'stop_loss': float(sig.stop_loss) if sig.stop_loss else None,
        'take_profit_1': float(sig.take_profit_1) if sig.take_profit_1 else None,
        'take_profit_2': float(sig.take_profit_2) if sig.take_profit_2 else None,
        'take_profit_3': float(sig.take_profit_3) if sig.take_profit_3 else None,
        'current_price': current_price or fallback_price,
        'indicators': indicators,
        'patterns': patterns,
        'quantitative': quantitative,
        'ml_predictions': ml_predictions,
        'strategy': strategy_full,
        'velocity': velocity,
        'money_mgmt': money_mgmt,
        'signal_contributions': contributions,
        'algorithm_versions': sig.algorithm_versions or {},
        'errors': [],
    })


def _cached_score_response(asset_id):
    """Return latest cached TradingSignal as score-compatible response."""
    from app.models.signal import TradingSignal

    timeframe = request.args.get('timeframe', '1D')

    sig = TradingSignal.query.filter_by(
        asset_id=asset_id, timeframe=timeframe, status='active',
    ).order_by(TradingSignal.created_at.desc()).first()

    if not sig:
        sig = TradingSignal.query.filter_by(asset_id=asset_id)\
            .order_by(TradingSignal.created_at.desc()).first()

    if not sig:
        return jsonify({
            'asset_id': asset_id,
            'ml_mode': 'view_only',
            'cached': True,
            'message': 'Belum ada data score. Jalankan bulk scan dari Admin ML.',
            'signal': 'HOLD',
            'score': 0,
            'confidence': 'Low',
        })

    return jsonify({
        'asset_id': asset_id,
        'timeframe': sig.timeframe or timeframe,
        'ml_mode': 'view_only',
        'cached': True,
        'cached_at': sig.created_at.isoformat() if sig.created_at else None,
        'signal': sig.signal_type,
        'score': float(sig.score),
        'confidence': sig.confidence,
        'recommended_strategy': sig.recommended_strategy,
        'safety_rating': sig.safety_rating,
        'entry_price': float(sig.entry_price) if sig.entry_price else None,
        'stop_loss': float(sig.stop_loss) if sig.stop_loss else None,
        'take_profit_1': float(sig.take_profit_1) if sig.take_profit_1 else None,
    })
