"""Settings API endpoints."""
from __future__ import annotations

import sys
import time

import flask
from flask import Blueprint, current_app, jsonify, request, session
from flask_login import login_required
from app.helpers.auth import admin_required, get_current_user_id
from sqlalchemy import func

from app.extensions import db
from app.models.settings import AppSettings, CapitalAllocation

api_settings_bp = Blueprint('api_settings', __name__)
_start_time = time.time()


@api_settings_bp.route('/mode', methods=['POST'])
@login_required
def switch_asset_mode():
    """Switch between crypto, stock, and stock_us mode — saved to UserSettings + session.

    Validates against user's allowed_markets — admin always has full access.
    """
    from app.models.user_settings import UserSettings
    from app.helpers.asset_filter import get_allowed_markets
    data = request.get_json() or {}
    mode = data.get('mode', '').lower()
    if mode not in ('crypto', 'stock', 'stock_us'):
        return jsonify({'error': 'Mode harus crypto, stock, atau stock_us'}), 400

    # Check if user has access to this market
    uid = get_current_user_id()
    allowed = get_allowed_markets(uid)
    if mode not in allowed:
        labels = {'crypto': 'Crypto', 'stock': 'Saham', 'stock_us': 'Saham US'}
        return jsonify({
            'error': f'Akses ke {labels.get(mode, mode)} belum diaktifkan untuk akun Anda'
        }), 403

    session['asset_mode'] = mode
    # Persist to UserSettings
    if uid:
        UserSettings.set(uid, 'asset_mode', mode, 'display')
    labels = {'crypto': 'Crypto', 'stock': 'Saham', 'stock_us': 'Saham US'}
    return jsonify({'asset_mode': mode, 'label': labels.get(mode, mode)})


@api_settings_bp.route('/mode', methods=['GET'])
@login_required
def get_asset_mode():
    """Get current asset mode from UserSettings (fallback session)."""
    from app.models.user_settings import UserSettings
    from app.helpers.asset_filter import get_allowed_markets
    uid = get_current_user_id()
    if uid:
        mode = UserSettings.get(uid, 'asset_mode', session.get('asset_mode', 'stock'))
    else:
        mode = session.get('asset_mode', 'stock')
    labels = {'crypto': 'Crypto', 'stock': 'Saham', 'stock_us': 'Saham US'}
    allowed = get_allowed_markets(uid) if uid else ['stock']
    return jsonify({
        'asset_mode': mode,
        'label': labels.get(mode, mode),
        'allowed_markets': allowed,
    })


@api_settings_bp.route('/color-mode', methods=['POST'])
@login_required
def set_color_mode():
    """Switch between dark and light color mode — saved to UserSettings + session."""
    from app.models.user_settings import UserSettings
    data = request.get_json() or {}
    mode = data.get('mode', 'dark')
    if mode not in ('dark', 'light'):
        mode = 'dark'
    session['color_mode'] = mode
    # Persist to UserSettings
    uid = get_current_user_id()
    if uid:
        UserSettings.set(uid, 'color_mode', mode, 'display')
    return jsonify({'color_mode': mode})


@api_settings_bp.route('/color-mode', methods=['GET'])
@login_required
def get_color_mode():
    """Get current color mode from UserSettings (fallback session)."""
    from app.models.user_settings import UserSettings
    uid = get_current_user_id()
    if uid:
        mode = UserSettings.get(uid, 'color_mode', session.get('color_mode', 'dark'))
    else:
        mode = session.get('color_mode', 'dark')
    return jsonify({'color_mode': mode})


@api_settings_bp.route('/', methods=['GET'])
@admin_required
def get_all_settings():
    """Get all settings."""
    settings = AppSettings.query.all()
    return jsonify([s.to_dict() for s in settings])


@api_settings_bp.route('/<key>', methods=['GET'])
@admin_required
def get_setting(key):
    """Get a specific setting."""
    val = AppSettings.get(key)
    if val is None:
        return jsonify({'error': f'Setting {key} not found'}), 404
    return jsonify({'key': key, 'value': val})


@api_settings_bp.route('/', methods=['POST', 'PUT'])
@admin_required
def update_setting():
    """Update a setting."""
    data = request.get_json() or {}
    key = data.get('key')
    value = data.get('value')
    category = data.get('category', 'system')
    description = data.get('description', '')

    if not key:
        return jsonify({'error': 'key is required'}), 400

    AppSettings.set(key, value, category, description)
    return jsonify({'key': key, 'value': value, 'message': 'Updated'})


@api_settings_bp.route('/data-source', methods=['POST'])
@admin_required
def update_data_source():
    """Update the active data source."""
    data = request.get_json() or {}
    source = data.get('source')
    valid_sources = ('coingecko', 'indodax', 'tokocrypto', 'yahoo')
    if source not in valid_sources:
        return jsonify({'error': f'Invalid source. Use {", ".join(valid_sources)}'}), 400

    # Block sources whose provider is not yet implemented
    if source == 'tokocrypto':
        return jsonify({'error': 'Tokocrypto provider belum tersedia. Coming soon!'}), 400

    AppSettings.set('active_data_source', source, 'data_source',
                    'Primary data source for OHLCV sync')
    return jsonify({'active_data_source': source})


@api_settings_bp.route('/data-source/api-keys', methods=['POST'])
@admin_required
def save_api_key():
    """Save API key for a data source."""
    data = request.get_json() or {}
    source = data.get('source')
    if source not in ('coingecko', 'indodax', 'tokocrypto'):
        return jsonify({'error': 'Invalid source'}), 400

    api_key = (data.get('api_key') or '').strip()
    api_secret = (data.get('api_secret') or '').strip()

    if not api_key:
        return jsonify({'error': 'API key is required'}), 400

    AppSettings.set(f'{source}_api_key', api_key, 'data_source',
                    f'API key for {source}')
    if api_secret:
        AppSettings.set(f'{source}_api_secret', api_secret, 'data_source',
                        f'API secret for {source}')

    masked = api_key[:4] + '****' + api_key[-4:] if len(api_key) >= 8 else '****'
    return jsonify({'success': True, 'masked_key': masked})


@api_settings_bp.route('/data-source/api-keys', methods=['DELETE'])
@admin_required
def delete_api_key():
    """Remove API key for a data source."""
    data = request.get_json() or {}
    source = data.get('source')
    if source not in ('coingecko', 'indodax', 'tokocrypto'):
        return jsonify({'error': 'Invalid source'}), 400

    setting = AppSettings.query.get(f'{source}_api_key')
    if setting:
        db.session.delete(setting)
    secret_setting = AppSettings.query.get(f'{source}_api_secret')
    if secret_setting:
        db.session.delete(secret_setting)
    db.session.commit()

    return jsonify({'success': True})


@api_settings_bp.route('/data-source/sync-config', methods=['POST'])
@admin_required
def save_sync_config():
    """Save sync configuration for a data source."""
    data = request.get_json() or {}
    source = data.get('source')
    if source not in ('coingecko', 'indodax', 'tokocrypto'):
        return jsonify({'error': 'Invalid source'}), 400

    cfg = data.get('config', {})
    if not cfg:
        return jsonify({'error': 'config object is required'}), 400

    # Validate numeric fields
    fields = {
        'retry_max': (0, 10),
        'retry_base_delay': (0.1, 60),
        'retry_max_delay': (1, 300),
        'rate_delay': (0, 10),
        'timeout': (5, 300),
    }
    clean = {}
    for key, (lo, hi) in fields.items():
        if key in cfg:
            try:
                val = float(cfg[key])
            except (ValueError, TypeError):
                return jsonify({'error': f'{key} must be numeric'}), 400
            if val < lo or val > hi:
                return jsonify({'error': f'{key} must be between {lo} and {hi}'}), 400
            clean[key] = int(val) if key in ('retry_max', 'timeout') else round(val, 2)

    if not clean:
        return jsonify({'error': 'No valid config fields provided'}), 400

    # Merge with existing saved config
    existing = AppSettings.get(f'{source}_sync_config', {}) or {}
    merged = {**existing, **clean}
    AppSettings.set(f'{source}_sync_config', merged, 'data_source',
                    f'Sync configuration for {source}')

    # Return full merged config (saved + defaults)
    defaults = current_app.config.get('SYNC_CONFIG_DEFAULTS', {}).get(source, {})
    full = {**defaults, **merged}
    return jsonify({'source': source, 'config': full})


@api_settings_bp.route('/data-source/sync-config/<source>', methods=['GET'])
@admin_required
def get_sync_config(source):
    """Get sync configuration for a data source (merged: saved + defaults)."""
    if source not in ('coingecko', 'indodax', 'tokocrypto'):
        return jsonify({'error': 'Invalid source'}), 400

    defaults = current_app.config.get('SYNC_CONFIG_DEFAULTS', {}).get(source, {})
    saved = AppSettings.get(f'{source}_sync_config', {}) or {}
    full = {**defaults, **saved}
    return jsonify({'source': source, 'config': full})


@api_settings_bp.route('/data-source/sync-config', methods=['DELETE'])
@admin_required
def reset_sync_config():
    """Reset sync config for a source to defaults."""
    data = request.get_json() or {}
    source = data.get('source')
    if source not in ('coingecko', 'indodax', 'tokocrypto'):
        return jsonify({'error': 'Invalid source'}), 400

    setting = AppSettings.query.get(f'{source}_sync_config')
    if setting:
        db.session.delete(setting)
        db.session.commit()

    defaults = current_app.config.get('SYNC_CONFIG_DEFAULTS', {}).get(source, {})
    return jsonify({'source': source, 'config': defaults, 'message': 'Reset to defaults'})


@api_settings_bp.route('/fees', methods=['POST'])
@login_required
def update_fees():
    """Update transaction fees (crypto or stock) — saved per-user in UserSettings."""
    from app.models.user_settings import UserSettings
    data = request.get_json() or {}
    fee_type = data.get('type', 'crypto')  # 'crypto', 'stock', or 'stock_us'
    uid = get_current_user_id()

    def _set_fee(key, value):
        """Save fee to UserSettings (per-user) or AppSettings (admin fallback)."""
        if uid:
            UserSettings.set(uid, key, float(value), 'fees')
        else:
            AppSettings.set(key, float(value), 'fees')

    def _get_fee(key, default):
        """Read fee from UserSettings (per-user) or AppSettings."""
        if uid:
            val = UserSettings.get(uid, key)
            if val is not None:
                return float(val)
        return float(AppSettings.get(key, default))

    if fee_type == 'stock_us':
        buy_fee = data.get('stock_us_buy_fee_pct')
        sell_fee = data.get('stock_us_sell_fee_pct')
        broker = data.get('stock_us_broker')

        if buy_fee is not None:
            _set_fee('stock_us_buy_fee_pct', buy_fee)
        if sell_fee is not None:
            _set_fee('stock_us_sell_fee_pct', sell_fee)
        if broker:
            if uid:
                UserSettings.set(uid, 'stock_us_broker', broker, 'fees')
            else:
                AppSettings.set('stock_us_broker', broker, 'fees')

        return jsonify({
            'stock_us_buy_fee_pct': _get_fee('stock_us_buy_fee_pct', 0.0),
            'stock_us_sell_fee_pct': _get_fee('stock_us_sell_fee_pct', 0.0),
            'stock_us_broker': (UserSettings.get(uid, 'stock_us_broker') if uid else None)
                               or AppSettings.get('stock_us_broker', 'gotrade'),
        })
    elif fee_type == 'stock':
        buy_fee = data.get('stock_buy_fee_pct')
        sell_fee = data.get('stock_sell_fee_pct')
        sell_tax = data.get('stock_sell_tax_pct')
        broker = data.get('stock_broker')

        if buy_fee is not None:
            _set_fee('stock_buy_fee_pct', buy_fee)
        if sell_fee is not None:
            _set_fee('stock_sell_fee_pct', sell_fee)
        if sell_tax is not None:
            _set_fee('stock_sell_tax_pct', sell_tax)
        if broker:
            if uid:
                UserSettings.set(uid, 'stock_broker', broker, 'fees')
            else:
                AppSettings.set('stock_broker', broker, 'fees')

        return jsonify({
            'stock_buy_fee_pct': _get_fee('stock_buy_fee_pct', 0.15),
            'stock_sell_fee_pct': _get_fee('stock_sell_fee_pct', 0.25),
            'stock_sell_tax_pct': _get_fee('stock_sell_tax_pct', 0.10),
            'stock_broker': (UserSettings.get(uid, 'stock_broker') if uid else None)
                            or AppSettings.get('stock_broker', 'growin'),
        })
    else:
        buy_fee = data.get('buy_fee_pct')
        sell_fee = data.get('sell_fee_pct')

        if buy_fee is not None:
            _set_fee('buy_fee_pct', buy_fee)
        if sell_fee is not None:
            _set_fee('sell_fee_pct', sell_fee)

        return jsonify({
            'buy_fee_pct': _get_fee('buy_fee_pct', 0.31),
            'sell_fee_pct': _get_fee('sell_fee_pct', 0.31),
        })


@api_settings_bp.route('/capital', methods=['GET'])
@login_required
def get_all_capital_allocations():
    """Get all capital allocations with asset info."""
    allocations = CapitalAllocation.query.filter_by(user_id=get_current_user_id()).all()
    result = []
    for a in allocations:
        d = a.to_dict()
        d['coin_name'] = a.asset.name if a.asset else a.asset_id
        d['coin_symbol'] = a.asset.symbol.upper() if a.asset else ''
        d['coin_icon'] = getattr(a.asset, 'icon_thumb_url', '') or '' if a.asset else ''
        result.append(d)
    return jsonify(result)


@api_settings_bp.route('/capital/<asset_id>', methods=['POST'])
@login_required
def update_capital_allocation(asset_id):
    """Update capital allocation for a asset."""
    data = request.get_json() or {}

    alloc = CapitalAllocation.query.filter_by(asset_id=asset_id, user_id=get_current_user_id()).first()
    if not alloc:
        alloc = CapitalAllocation(asset_id=asset_id, user_id=get_current_user_id())
        db.session.add(alloc)

    if 'allocated_capital_idr' in data:
        alloc.allocated_capital_idr = data['allocated_capital_idr']
    if 'active_capital_pct' in data:
        alloc.active_capital_pct = data['active_capital_pct']
    if 'reserve_capital_pct' in data:
        alloc.reserve_capital_pct = data['reserve_capital_pct']

    db.session.commit()
    return jsonify(alloc.to_dict())


@api_settings_bp.route('/capital/<asset_id>', methods=['DELETE'])
@login_required
def delete_capital_allocation(asset_id):
    """Remove per-asset allocation (reverts to global default)."""
    alloc = CapitalAllocation.query.filter_by(asset_id=asset_id, user_id=get_current_user_id()).first()
    if not alloc:
        return jsonify({'error': 'Allocation not found'}), 404
    db.session.delete(alloc)
    db.session.commit()
    return jsonify({'message': f'Allocation for {asset_id} removed', 'asset_id': asset_id})


@api_settings_bp.route('/ml-mode', methods=['GET'])
@login_required
def get_ml_mode():
    """Get current ML compute mode."""
    from app.helpers.ml_mode import get_ml_mode as _get
    return jsonify({'ml_mode': _get()})


@api_settings_bp.route('/ml-mode', methods=['POST'])
@admin_required
def set_ml_mode():
    """Set ML compute mode (admin only)."""
    data = request.get_json() or {}
    mode = data.get('mode', '')
    if mode not in ('live', 'view_only'):
        return jsonify({'error': "Mode harus 'live' atau 'view_only'"}), 400

    AppSettings.set('ml_compute_mode', mode, 'system',
                    'ML compute mode: live or view_only')
    return jsonify({'ml_mode': mode})


@api_settings_bp.route('/ml-stats', methods=['GET'])
@admin_required
def get_ml_stats():
    """Get ML model stats: record counts, last computed timestamps."""
    from app.models.signal import TradingSignal
    from app.models.range_score import RangeTradingScore
    from app.helpers.ml_mode import get_ml_mode as _get
    from sqlalchemy import func

    # TradingSignal stats
    sig_count = TradingSignal.query.count()
    sig_active = TradingSignal.query.filter_by(status='active').count()
    sig_latest = db.session.query(func.max(TradingSignal.created_at)).scalar()

    # RangeTradingScore stats
    rts_count = RangeTradingScore.query.count()
    rts_latest = db.session.query(func.max(RangeTradingScore.computed_at)).scalar()
    rts_cycle_count = RangeTradingScore.query.filter(
        RangeTradingScore.cycle_complete_count.isnot(None)
    ).count()
    rts_cycle_latest = db.session.query(
        func.max(RangeTradingScore.cycle_scanned_at)
    ).scalar()

    # BullishMomentumScore stats
    bms_count = 0
    bms_latest = None
    try:
        from app.models.bullish_score import BullishMomentumScore
        bms_count = BullishMomentumScore.query.count()
        bms_latest = db.session.query(
            func.max(BullishMomentumScore.computed_at)
        ).scalar()
    except Exception:
        pass

    return jsonify({
        'ml_mode': _get(),
        'trading_signals': {
            'total': sig_count,
            'active': sig_active,
            'last_computed': sig_latest.isoformat() if sig_latest else None,
        },
        'range_trading_scores': {
            'total': rts_count,
            'last_computed': rts_latest.isoformat() if rts_latest else None,
            'with_cycles': rts_cycle_count,
            'last_cycle_scan': rts_cycle_latest.isoformat() if rts_cycle_latest else None,
        },
        'bullish_momentum_scores': {
            'total': bms_count,
            'last_computed': bms_latest.isoformat() if bms_latest else None,
        },
    })


@api_settings_bp.route('/sync-status', methods=['GET'])
@admin_required
def get_sync_status():
    """Get sync status: last sync, record counts, recent activity."""
    from app.models.asset import Asset, AssetProfile
    from app.models.ohlcv import OHLCVData

    from app.helpers.asset_filter import apply_asset_filter
    active_source = AppSettings.get('active_data_source', 'coingecko')

    # Last sync time (latest profile fetched_at)
    last_profile = AssetProfile.query.order_by(
        AssetProfile.fetched_at.desc()
    ).first()
    last_sync_at = last_profile.fetched_at.isoformat() if last_profile and last_profile.fetched_at else None

    # Counts
    total_coins = apply_asset_filter(Asset.query.filter_by(is_active=True)).count()
    total_coins_with_profile = db.session.query(
        func.count(func.distinct(AssetProfile.asset_id))
    ).scalar()
    total_ohlcv = OHLCVData.query.count()

    # Oldest OHLCV
    oldest = OHLCVData.query.order_by(OHLCVData.datetime_wib.asc()).first()
    oldest_data = oldest.datetime_wib.isoformat() if oldest and oldest.datetime_wib else None

    # Per-timeframe breakdown
    tf_stats = db.session.query(
        OHLCVData.timeframe,
        func.count(OHLCVData.id).label('count'),
        func.min(OHLCVData.datetime_wib).label('oldest'),
        func.max(OHLCVData.datetime_wib).label('newest'),
    ).group_by(OHLCVData.timeframe).all()

    per_timeframe = []
    for tf in tf_stats:
        per_timeframe.append({
            'timeframe': tf.timeframe,
            'count': tf.count,
            'oldest': tf.oldest.isoformat() if tf.oldest else None,
            'newest': tf.newest.isoformat() if tf.newest else None,
        })

    # Recent 10 profile updates
    recent = AssetProfile.query.join(Asset).order_by(
        AssetProfile.fetched_at.desc()
    ).limit(10).all()
    recent_profiles = []
    for p in recent:
        asset = Asset.query.get(p.asset_id)
        recent_profiles.append({
            'asset_id': p.asset_id,
            'coin_name': asset.name if asset else p.asset_id,
            'coin_symbol': asset.symbol.upper() if asset else '',
            'icon': getattr(asset, 'icon_thumb_url', '') or '' if asset else '',
            'current_price_idr': float(p.current_price_idr) if p.current_price_idr else None,
            'fetched_at': p.fetched_at.isoformat() if p.fetched_at else None,
        })

    return jsonify({
        'active_source': active_source,
        'last_sync_at': last_sync_at,
        'total_coins': total_coins,
        'total_coins_synced': total_coins_with_profile,
        'total_ohlcv_records': total_ohlcv,
        'oldest_data': oldest_data,
        'per_timeframe': per_timeframe,
        'recent_profiles': recent_profiles,
    })


@api_settings_bp.route('/system-info', methods=['GET'])
@admin_required
def get_system_info():
    """Get system information: versions, DB stats, uptime."""
    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, TradeHistory
    from app.models.prediction_queue import PredictionQueue
    from app.models.watchlist import Watchlist

    active_source = AppSettings.get('active_data_source', 'coingecko')

    # DB stats — counts
    db_stats = {
        'assets': Asset.query.count(),
        'coins_active': Asset.query.filter_by(is_active=True).count(),
        'profiles': AssetProfile.query.count(),
        'ohlcv': OHLCVData.query.count(),
        'signals': TradingSignal.query.count(),
        'signals_active': TradingSignal.query.filter_by(status='active').count(),
        'portfolio': Portfolio.query.count(),
        'trades': TradeHistory.query.count(),
        'queue': PredictionQueue.query.count(),
        'watchlist': Watchlist.query.count(),
    }

    # DB size (approximate via SHOW TABLE STATUS)
    db_size_mb = 0
    try:
        result = db.session.execute(
            db.text("SELECT SUM(data_length + index_length) / 1024 / 1024 "
                     "AS size_mb FROM information_schema.tables "
                     "WHERE table_schema = DATABASE()")
        )
        row = result.fetchone()
        if row and row[0]:
            db_size_mb = round(float(row[0]), 2)
    except Exception:
        pass

    uptime_seconds = int(time.time() - _start_time)
    hours, remainder = divmod(uptime_seconds, 3600)
    minutes, secs = divmod(remainder, 60)
    uptime_str = f'{hours}h {minutes}m {secs}s'

    return jsonify({
        'app_version': '1.0.0',
        'python_version': sys.version.split()[0],
        'flask_version': flask.__version__,
        'active_source': active_source,
        'db_stats': db_stats,
        'db_size_mb': db_size_mb,
        'uptime': uptime_str,
        'uptime_seconds': uptime_seconds,
    })
