"""Investasi Kamu - Trading Analysis & Prediction Platform."""
import os
import sys
import logging
from flask import Flask, session
from dotenv import load_dotenv

load_dotenv()

logger = logging.getLogger(__name__)

# ── Required dependencies ─────────────────────────────────────────────
# Semua library yang WAJIB terinstall sebelum app bisa jalan.
# Format: (import_name, pip_name, deskripsi)
REQUIRED_DEPENDENCIES = [
    # Flask & Web
    ('flask', 'flask', 'Web framework'),
    ('flask_sqlalchemy', 'flask-sqlalchemy', 'Flask SQLAlchemy ORM'),
    ('flask_cors', 'flask-cors', 'CORS support'),
    ('flask_login', 'flask-login', 'Authentication'),
    # Database
    ('pymysql', 'pymysql', 'MySQL driver'),
    ('cryptography', 'cryptography', 'SSL/TLS for DB connections'),
    # Data Processing
    ('pandas', 'pandas', 'Data processing'),
    ('numpy', 'numpy', 'Numerical computing'),
    # HTTP & API
    ('requests', 'requests', 'HTTP client'),
    ('yfinance', 'yfinance', 'Yahoo Finance data'),
    # Machine Learning
    ('sklearn', 'scikit-learn', 'ML framework'),
    ('xgboost', 'xgboost', 'XGBoost model'),
    ('lightgbm', 'lightgbm', 'LightGBM model'),
    ('catboost', 'catboost', 'CatBoost model'),
    ('scipy', 'scipy', 'Scientific computing'),
    ('joblib', 'joblib', 'Model persistence'),
    ('optuna', 'optuna', 'Hyperparameter optimization'),
    ('shap', 'shap', 'Feature importance (SHAP)'),
    ('pywt', 'PyWavelets', 'Wavelet decomposition'),
    # Background Scheduler
    ('apscheduler', 'apscheduler', 'Background task scheduler'),
    # Markdown
    ('mistune', 'mistune', 'Markdown rendering'),
    # Utilities
    ('dotenv', 'python-dotenv', 'Environment variables'),
    ('dateutil', 'python-dateutil', 'Date utilities'),
]


def _check_dependencies():
    """Validate all required libraries are installed before app starts.

    Raises SystemExit with clear error message if any dependency is missing.
    """
    missing = []
    for import_name, pip_name, description in REQUIRED_DEPENDENCIES:
        try:
            __import__(import_name)
        except ImportError:
            missing.append((pip_name, description))

    if missing:
        print('\n' + '=' * 60, file=sys.stderr)
        print(' ❌  MISSING DEPENDENCIES', file=sys.stderr)
        print('=' * 60, file=sys.stderr)
        print(f'\n  {len(missing)} required package(s) not found:\n', file=sys.stderr)
        for pip_name, desc in missing:
            print(f'    • {pip_name:<20s}  — {desc}', file=sys.stderr)
        print(f'\n  Fix with:', file=sys.stderr)
        print(f'    pip install -r requirements.txt\n', file=sys.stderr)
        print('=' * 60 + '\n', file=sys.stderr)
        sys.exit(1)
    else:
        logger.info('✅ All %d dependencies verified', len(REQUIRED_DEPENDENCIES))


# Run dependency check immediately on module load
_check_dependencies()


def create_app(config_name=None):
    """Flask application factory."""
    app = Flask(__name__,
                template_folder='templates',
                static_folder='static')

    # Load config
    if config_name is None:
        config_name = os.getenv('FLASK_ENV', 'development')

    if config_name == 'production':
        app.config.from_object('config.production')
    else:
        app.config.from_object('config.development')

    # Reverse proxy support (Nginx → Gunicorn)
    if not app.debug:
        from werkzeug.middleware.proxy_fix import ProxyFix
        app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)

    # Initialize extensions
    from app.extensions import db, cors, login_manager
    db.init_app(app)

    # CORS — restrict di production, open di development
    cors_origins = app.config.get('ALLOWED_HOSTS', ['*'])
    if not app.debug:
        cors_origins = [f'https://{h}' for h in cors_origins if h not in ('localhost', '127.0.0.1')]
    else:
        cors_origins = '*'
    cors.init_app(app, origins=cors_origins)

    login_manager.init_app(app)

    # Flask-Login user loader (dual Admin/User)
    @login_manager.user_loader
    def load_user(user_id):
        from app.models.admin import Admin
        from app.models.user import User
        if user_id.startswith('admin:'):
            return Admin.query.get(int(user_id.split(':')[1]))
        elif user_id.startswith('user:'):
            user = User.query.get(int(user_id.split(':')[1]))
            if user and user.is_active and user.is_approved:
                return user
        return None

    # Register blueprints
    _register_blueprints(app)

    # Error handlers
    _register_error_handlers(app)

    # Template filters (Indonesian locale) & globals
    _register_template_filters(app)
    _register_template_globals(app)

    # Context processor — inject asset_mode into all templates
    _register_context_processors(app)

    # Dynamic market DB routing — switches schema per request based on toggle
    from app.helpers.market_db import register_before_request
    register_before_request(app)

    # Create tables in all 3 market databases + app database
    with app.app_context():
        from app.models import base  # noqa: F401 - ensure models are imported
        from app.helpers.market_db import switch_market_schema
        from app.models._schema import MARKET_DB_MAP
        for mode_key in MARKET_DB_MAP:
            switch_market_schema(mode_key)
            db.create_all()
        switch_market_schema('stock')  # reset to default (Saham Indonesia)

        # Seed tier config defaults (features + pricing) if tables are empty
        from app.helpers.tier_config import seed_defaults
        seed_defaults()

        # Seed landing page CMS content defaults
        from app.helpers.landing_content import seed_landing_defaults
        seed_landing_defaults()

    # Background scheduler (outcome eval, sync, signal scan)
    from app.scheduler import init_scheduler
    init_scheduler(app)

    return app


def _register_error_handlers(app):
    """Register custom error pages."""
    from flask import render_template, jsonify, request

    @app.errorhandler(404)
    def not_found(e):
        if request.path.startswith('/api/'):
            return jsonify({'error': 'Not found'}), 404
        return render_template('errors/404.html'), 404

    @app.errorhandler(500)
    def server_error(e):
        if request.path.startswith('/api/'):
            return jsonify({'error': 'Internal server error'}), 500
        return render_template('errors/500.html'), 500


def _register_blueprints(app):
    """Register all Flask blueprints."""
    from app.blueprints.dashboard.routes import dashboard_bp
    from app.blueprints.market.routes import market_bp
    from app.blueprints.data.routes import data_bp
    from app.blueprints.settings.routes import settings_bp
    from app.blueprints.api.v1.market import api_market_bp
    from app.blueprints.api.v1.settings import api_settings_bp
    from app.blueprints.api.v1.export import api_export_bp
    from app.blueprints.analysis.routes import analysis_bp
    from app.blueprints.api.v1.analysis import api_analysis_bp
    from app.blueprints.timeline.routes import timeline_bp
    from app.blueprints.api.v1.timeline import api_timeline_bp
    from app.blueprints.api.v1.portfolio import api_portfolio_bp
    from app.blueprints.api.v1.dashboard import api_dashboard_bp
    from app.blueprints.portfolio.routes import portfolio_bp
    from app.blueprints.auth.routes import auth_bp
    from app.blueprints.admin.routes import admin_bp
    from app.blueprints.api.v1.admin import api_admin_bp
    from app.blueprints.api.v1.sync import api_sync_bp
    from app.blueprints.tools.routes import tools_bp
    from app.blueprints.api.v1.subscription import api_subscription_bp
    from app.blueprints.api.v1.tier_admin import api_tier_admin_bp

    app.register_blueprint(auth_bp)
    app.register_blueprint(admin_bp)
    app.register_blueprint(tools_bp)
    app.register_blueprint(dashboard_bp)
    app.register_blueprint(market_bp, url_prefix='/market')
    app.register_blueprint(data_bp, url_prefix='/data')
    app.register_blueprint(analysis_bp, url_prefix='/analysis')
    app.register_blueprint(timeline_bp, url_prefix='/timeline')
    app.register_blueprint(portfolio_bp, url_prefix='/portfolio')
    app.register_blueprint(settings_bp, url_prefix='/settings')
    app.register_blueprint(api_market_bp, url_prefix='/api/v1/market')
    app.register_blueprint(api_settings_bp, url_prefix='/api/v1/settings')
    app.register_blueprint(api_export_bp, url_prefix='/api/v1/export')
    app.register_blueprint(api_analysis_bp, url_prefix='/api/v1/analysis')
    app.register_blueprint(api_timeline_bp, url_prefix='/api/v1/timeline')
    app.register_blueprint(api_portfolio_bp, url_prefix='/api/v1/portfolio')
    app.register_blueprint(api_dashboard_bp, url_prefix='/api/v1/dashboard')
    app.register_blueprint(api_admin_bp, url_prefix='/api/v1/admin')
    app.register_blueprint(api_sync_bp, url_prefix='/api/v1/sync')
    app.register_blueprint(api_subscription_bp)
    app.register_blueprint(api_tier_admin_bp)


def _register_context_processors(app):
    """Inject asset mode variables into all templates."""
    from flask_login import current_user

    @app.context_processor
    def inject_asset_mode():
        # Skip for API routes — they return JSON, not rendered templates.
        # Saves 2+ DB queries per API request.
        from flask import request as _req
        if _req.path.startswith('/api/'):
            return {}

        from app.models.settings import AppSettings
        from app.helpers.auth import is_admin as _is_admin
        mode = session.get('asset_mode', 'stock')
        if mode not in ('crypto', 'stock', 'stock_us'):
            mode = 'stock'
        color_mode = session.get('color_mode', 'dark')
        if color_mode not in ('dark', 'light'):
            color_mode = 'dark'
        theme_class = f'theme-{mode}-{color_mode}'

        app_names = {'crypto': 'Investasi Kamu', 'stock': 'Investasi Kamu', 'stock_us': 'Investasi Kamu'}
        mode_labels = {'crypto': 'Kripto', 'stock': 'Saham', 'stock_us': 'Saham US'}
        item_labels = {'crypto': 'Koin', 'stock': 'Saham', 'stock_us': 'Saham'}
        currency_map = {
            'crypto': ('Rp', 'IDR'),
            'stock': ('Rp', 'IDR'),
            'stock_us': ('$', 'USD'),
        }
        cur_symbol, cur_code = currency_map.get(mode, ('Rp', 'IDR'))

        # Auth context
        authenticated = current_user.is_authenticated
        admin_flag = _is_admin() if authenticated else False
        if authenticated:
            user_display = getattr(current_user, 'display_name', None) or current_user.username
        else:
            user_display = ''

        # Tier context — used by navigation to show/hide + badge features
        from app.models.user import User as _User
        if authenticated and isinstance(current_user._get_current_object(), _User):
            _user_tier = current_user.tier
            _user_tier_level = current_user.tier_level
        elif admin_flag:
            _user_tier = 'ultimate'
            _user_tier_level = 3
        else:
            _user_tier = 'guest'
            _user_tier_level = -1

        # tools_base: URL prefix for screener/tool links
        _tools_base = '/admin' if admin_flag else '/tools'

        # Allowed markets — which toggle buttons to show
        from app.helpers.asset_filter import get_allowed_markets
        if admin_flag:
            _allowed_markets = ['stock', 'stock_us', 'crypto']
        elif authenticated and isinstance(current_user._get_current_object(), _User):
            from app.helpers.auth import get_current_user_id as _uid
            _allowed_markets = get_allowed_markets(_uid())
        else:
            _allowed_markets = ['stock']

        # ML mode
        from app.helpers.ml_mode import get_ml_mode
        ml_mode = get_ml_mode()

        return {
            'asset_mode': mode,
            'color_mode': color_mode,
            'theme_class': theme_class,
            'is_stock_mode': mode in ('stock', 'stock_us'),
            'is_crypto_mode': mode == 'crypto',
            'is_stock_us_mode': mode == 'stock_us',
            'is_stock_idx_mode': mode == 'stock',
            'mode_label': mode_labels.get(mode, 'Kripto'),
            'item_label': item_labels.get(mode, 'Koin'),
            'app_name': app_names.get(mode, 'Investasi Kamu'),
            'stock_icon_source': AppSettings.get('stock_icon_source', 'stockbit'),
            'currency_symbol': cur_symbol,
            'currency_code': cur_code,
            'usd_idr_rate': float(AppSettings.get('usd_idr_rate', 16000)),
            # Auth
            'is_authenticated': authenticated,
            'is_admin': admin_flag,
            'current_user': current_user,
            'user_display_name': user_display,
            # Tier
            'user_tier': _user_tier,
            'user_tier_level': _user_tier_level,
            'tools_base': _tools_base,
            # Market access
            'allowed_markets': _allowed_markets,
            # ML
            'ml_mode': ml_mode,
            'is_ml_view_only': ml_mode == 'view_only',
        }


def _swap_separators(s):
    """Swap comma/dot separators from English to Indonesian locale.

    English: 1,234,567.89  →  Indonesian: 1.234.567,89
    """
    return s.replace(',', '\x00').replace('.', ',').replace('\x00', '.')


def _register_template_globals(app):
    """Register Jinja2 global functions available in all templates."""
    from app.services.icon_downloader import get_stock_remote_icon_url

    def coin_icon(asset):
        """Return the best available icon URL for a asset/stock.

        Priority: icon_thumb_url → remote stock URL (Stockbit/Growin) → None.
        Works with both Asset model objects and dicts.
        """
        if hasattr(asset, 'icon_thumb_url'):
            url = asset.icon_thumb_url
            symbol = asset.symbol
            asset_type = getattr(asset, 'asset_type', 'crypto')
        elif isinstance(asset, dict):
            url = asset.get('icon_thumb_url')
            symbol = asset.get('symbol')
            asset_type = asset.get('asset_type', 'crypto')
        else:
            return None

        if url:
            return url
        if asset_type in ('stock', 'stock_us') and symbol:
            return get_stock_remote_icon_url(symbol)
        return None

    app.jinja_env.globals['coin_icon'] = coin_icon


def _register_template_filters(app):
    """Register Jinja2 template filters for Indonesian locale formatting."""

    @app.template_filter('price')
    def filter_price(val):
        """Smart price format with up to 6 decimals, no trailing zeros.

        1234567     → 1.234.567
        1234.50     → 1.234,5
        123.456789  → 123,456789
        0.005678    → 0,005678
        0.000001    → 0,000001
        """
        try:
            v = float(val)
            # Format with 6 decimals, then strip trailing zeros
            s = '{:,.6f}'.format(v)
            # Remove trailing zeros after decimal point
            if '.' in s:
                s = s.rstrip('0').rstrip('.')
            return _swap_separators(s)
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('idr')
    def filter_idr(val):
        """Format integer with dot thousands: 1234567 → 1.234.567"""
        try:
            return _swap_separators('{:,.0f}'.format(float(val)))
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('idr2')
    def filter_idr2(val):
        """Format with 2 decimals: 1234567.89 → 1.234.567,89"""
        try:
            return _swap_separators('{:,.2f}'.format(float(val)))
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('idr8')
    def filter_idr8(val):
        """Format with 8 decimals (crypto qty): 0.00012345 → 0,00012345"""
        try:
            return _swap_separators('{:,.8f}'.format(float(val)))
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('sidr')
    def filter_sidr(val):
        """Signed integer with dot thousands: 1234567 → +1.234.567"""
        try:
            return _swap_separators('{:+,.0f}'.format(float(val)))
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('num1')
    def filter_num1(val):
        """Format 1 decimal: 12345.6 → 12.345,6"""
        try:
            return _swap_separators('{:,.1f}'.format(float(val)))
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('num2')
    def filter_num2(val):
        """Format 2 decimals without thousands: 12.56 → 12,56"""
        try:
            return '{:.2f}'.format(float(val)).replace('.', ',')
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('num3')
    def filter_num3(val):
        """Format 3 decimals: 0.123 → 0,123"""
        try:
            return '{:.3f}'.format(float(val)).replace('.', ',')
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('num4')
    def filter_num4(val):
        """Format 4 decimals: 0.1222 → 0,1222"""
        try:
            return '{:.4f}'.format(float(val)).replace('.', ',')
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('pct')
    def filter_pct(val):
        """Percentage 1 decimal: 12.5 → 12,5%"""
        try:
            return '{:.1f}'.format(float(val)).replace('.', ',') + '%'
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('pct2')
    def filter_pct2(val):
        """Percentage 2 decimals: 12.56 → 12,56%"""
        try:
            return '{:.2f}'.format(float(val)).replace('.', ',') + '%'
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('spct')
    def filter_spct(val):
        """Signed percentage 1 decimal: 12.5 → +12,5%"""
        try:
            return '{:+.1f}'.format(float(val)).replace('.', ',') + '%'
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('spct2')
    def filter_spct2(val):
        """Signed percentage 2 decimals: -3.45 → -3,45%"""
        try:
            return '{:+.2f}'.format(float(val)).replace('.', ',') + '%'
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('abbr')
    def filter_abbr(val):
        """Abbreviate large numbers: 1.5T, 2.3B, 450M, or full Indonesian format."""
        try:
            v = float(val)
            if v >= 1e15:
                return _swap_separators('{:.1f}'.format(v / 1e15)) + 'Q'
            if v >= 1e12:
                return _swap_separators('{:.1f}'.format(v / 1e12)) + 'T'
            if v >= 1e9:
                return _swap_separators('{:.1f}'.format(v / 1e9)) + 'B'
            if v >= 1e6:
                return _swap_separators('{:.1f}'.format(v / 1e6)) + 'M'
            return _swap_separators('{:,.0f}'.format(v))
        except (ValueError, TypeError):
            return '-'

    @app.template_filter('wib')
    def filter_wib(val):
        """Format datetime as dd/mm/yy HH:MM WIB."""
        try:
            return val.strftime('%d/%m/%y %H:%M') + ' WIB'
        except (AttributeError, TypeError):
            return '-'

    @app.template_filter('wib_short')
    def filter_wib_short(val):
        """Format datetime as dd/mm HH:MM."""
        try:
            return val.strftime('%d/%m %H:%M')
        except (AttributeError, TypeError):
            return '-'

    @app.template_filter('timestamp_to_date')
    def filter_timestamp_to_date(val):
        """Convert Unix timestamp to dd/mm/yyyy."""
        try:
            from datetime import datetime as _dt
            return _dt.utcfromtimestamp(int(val)).strftime('%d/%m/%Y')
        except (ValueError, TypeError, OSError):
            return '-'
