"""
VYNDR — Consolidated Python Service
Master Flask app. Registers all blueprints. Health check. Rate limiting.
Self-documenting API. Single process on port 5001.
"""

import os
import sys
import json
import logging
from datetime import datetime

from flask import Flask, jsonify
from flask_cors import CORS
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format='[%(asctime)s] %(levelname)s %(name)s: %(message)s',
    datefmt='%Y-%m-%d %H:%M:%S'
)
logger = logging.getLogger('vyndr')

# Add utils to path for imports
sys.path.insert(0, os.path.dirname(__file__))

app = Flask(__name__)

# Request body size limit — 1MB default (OCR validates its own 10MB limit)
app.config['MAX_CONTENT_LENGTH'] = 1 * 1024 * 1024

# CORS — locked to ALLOWED_ORIGINS (Vercel domain + localhost)
ALLOWED_ORIGINS = os.environ.get('ALLOWED_ORIGINS', 'http://localhost:3000').split(',')
CORS(app, resources={r'/api/*': {
    'origins': ALLOWED_ORIGINS,
    'methods': ['GET', 'POST', 'OPTIONS'],
    'allow_headers': ['Authorization', 'Content-Type', 'X-API-Key'],
    'max_age': 3600
}})

# Rate limiting — real IP from X-Forwarded-For (Railway proxy)
def _get_real_ip():
    from flask import request as _req
    forwarded = _req.headers.get('X-Forwarded-For', '')
    if forwarded:
        return forwarded.split(',')[0].strip()
    return _req.remote_addr or '127.0.0.1'

limiter = Limiter(
    app=app,
    key_func=_get_real_ip,
    default_limits=["60 per minute"],
    storage_uri="memory://"
)

# Shadow mode — set to False after 2 weeks of verified accuracy
SHADOW_MODE = os.environ.get('SHADOW_MODE', 'true').lower() == 'true'


# --- Security: Headers, Logging, Error Handling ---

@app.after_request
def add_security_headers(response):
    """Add security headers to every response."""
    response.headers['X-Content-Type-Options'] = 'nosniff'
    response.headers['X-Frame-Options'] = 'DENY'
    response.headers['X-XSS-Protection'] = '1; mode=block'
    response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
    response.headers['Content-Security-Policy'] = "default-src 'self'"
    response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
    response.headers.pop('Server', None)
    return response


@app.before_request
def before_request_security():
    """Log every request for security monitoring."""
    try:
        from utils.security_logger import log_request
        from flask import request as _req
        log_request(_req)
    except Exception:
        pass  # Security logging must never block requests


@app.errorhandler(Exception)
def handle_exception(e):
    """Never expose internal errors in production."""
    from werkzeug.exceptions import HTTPException
    logger.error(f'[ERROR] Unhandled: {e}', exc_info=True)
    if isinstance(e, HTTPException):
        return jsonify({'error': e.description}), e.code
    if os.environ.get('FLASK_ENV') == 'production':
        return jsonify({'error': 'Internal server error'}), 500
    return jsonify({'error': str(e)}), 500


@app.errorhandler(404)
def not_found(e):
    return jsonify({'error': 'Endpoint not found'}), 404


@app.errorhandler(405)
def method_not_allowed(e):
    return jsonify({'error': 'Method not allowed'}), 405


@app.errorhandler(413)
def payload_too_large(e):
    return jsonify({'error': 'Request payload too large. Max 1MB (10MB for images).'}), 413


@app.errorhandler(429)
def rate_limited(e):
    return jsonify({'error': 'Rate limit exceeded. Try again later.'}), 429

# --- Register Blueprints ---

from blueprints.evolution import evolution_bp
app.register_blueprint(evolution_bp, url_prefix='/api/evolution')

# Import remaining blueprints (registered as they are built in later phases)
try:
    from blueprints.synergy import synergy_bp
    app.register_blueprint(synergy_bp, url_prefix='/api/synergy')
except ImportError:
    logger.info('[VYNDR] Synergy blueprint not yet available')

try:
    from blueprints.mlb import mlb_bp
    app.register_blueprint(mlb_bp, url_prefix='/api/mlb')
except ImportError:
    logger.info('[VYNDR] MLB blueprint not yet available')

try:
    from blueprints.nba_context import nba_context_bp
    app.register_blueprint(nba_context_bp, url_prefix='/api/nba')
except ImportError:
    logger.info('[VYNDR] NBA Context blueprint not yet available')

try:
    from blueprints.lineup_intelligence import lineup_bp
    app.register_blueprint(lineup_bp, url_prefix='/api/lineups')
except ImportError:
    logger.info('[VYNDR] Lineup Intelligence blueprint not yet available')

try:
    from blueprints.odds_scanner import odds_bp
    app.register_blueprint(odds_bp, url_prefix='/api/odds')
except ImportError:
    logger.info('[VYNDR] Odds Scanner blueprint not yet available')

try:
    from blueprints.calibration import calibration_bp
    app.register_blueprint(calibration_bp, url_prefix='/api/calibration')
except ImportError:
    logger.info('[VYNDR] Calibration blueprint not yet available')

try:
    from blueprints.resolution import resolution_bp
    app.register_blueprint(resolution_bp, url_prefix='/api/resolution')
except ImportError:
    logger.info('[VYNDR] Resolution blueprint not yet available')

try:
    from blueprints.image_grade import image_grade_bp
    app.register_blueprint(image_grade_bp, url_prefix='/api/grade')
except ImportError:
    logger.info('[VYNDR] Image Grade blueprint not yet available')

# --- Supplement Blueprints ---

try:
    from blueprints.coaching import coaching_bp
    app.register_blueprint(coaching_bp, url_prefix='/api/coaching')
except ImportError:
    logger.info('[VYNDR] Coaching blueprint not yet available')

try:
    from blueprints.redistribution import redistribution_bp
    app.register_blueprint(redistribution_bp, url_prefix='/api/redistribution')
except ImportError:
    logger.info('[VYNDR] Redistribution blueprint not yet available')

try:
    from blueprints.unconventional import unconventional_bp
    app.register_blueprint(unconventional_bp, url_prefix='/api/unconventional')
except ImportError:
    logger.info('[VYNDR] Unconventional blueprint not yet available')


# --- Health Check ---

@app.route('/health', methods=['GET'])
def health_check():
    """
    Health check endpoint for deployment monitoring.
    Checks connectivity to all dependent services.

    Returns:
        200 if all services healthy, 503 if any degraded.
    """
    services = {}

    # Supabase
    try:
        from utils.supabase_client import get_supabase_client
        client = get_supabase_client()
        services['supabase'] = 'ok' if client else 'not_configured'
    except Exception:
        services['supabase'] = 'error'

    # Redis
    try:
        import redis
        r = redis.from_url(os.environ.get('REDIS_URL', 'redis://127.0.0.1:6379'))
        r.ping()
        services['redis'] = 'ok'
    except Exception:
        services['redis'] = 'unavailable'

    # Odds API
    services['odds_api'] = 'configured' if os.environ.get('ODDS_API_KEY') else 'not_configured'

    # nba_api
    try:
        import nba_api
        services['nba_api'] = 'available'
    except ImportError:
        services['nba_api'] = 'not_installed'

    # Weather API (Open-Meteo — always available, no key)
    services['weather_api'] = 'ok'

    # MLB Stats API
    try:
        import statsapi
        services['mlb_stats_api'] = 'available'
    except ImportError:
        services['mlb_stats_api'] = 'not_installed'

    all_healthy = all(s in ('ok', 'available', 'configured') for s in services.values())
    return jsonify({
        'status': 'ok' if all_healthy else 'degraded',
        'version': '5.1',
        'shadow_mode': SHADOW_MODE,
        'services': services,
        'timestamp': datetime.utcnow().isoformat()
    }), 200 if all_healthy else 503


# --- Self-Documenting API ---

@app.route('/api/docs', methods=['GET'])
def api_docs():
    """
    Self-documenting API reference for frontend integration.
    Lists all available endpoints with method, path, and body schema.
    """
    return jsonify({
        'endpoints': {
            'health': {'method': 'GET', 'path': '/health'},
            'nba_grade': {
                'method': 'POST', 'path': '/api/nba/grade',
                'body': '{player_name, stat_type, line, over_under, user_id}'
            },
            'nba_sub_scores': {
                'method': 'GET',
                'path': '/api/nba/sub-scores/{player_id}/{game_id}'
            },
            'mlb_grade': {
                'method': 'POST', 'path': '/api/mlb/grade',
                'body': '{player_name, stat_type, line, over_under, pitcher_id?, user_id}'
            },
            'scan_slate': {'method': 'GET', 'path': '/api/odds/scan/{sport}'},
            'resolve_grades': {
                'method': 'POST',
                'path': '/api/calibration/resolve/{game_date}'
            },
            'brier_score': {
                'method': 'GET',
                'path': '/api/calibration/brier-score/{sport}'
            },
            'clv_report': {
                'method': 'GET',
                'path': '/api/calibration/clv/{sport}'
            },
            'blind_spots': {
                'method': 'GET',
                'path': '/api/calibration/blind-spots/{sport}'
            },
            'grade_from_image': {
                'method': 'POST', 'path': '/api/grade/from-image'
            },
            'parlay_grade': {
                'method': 'POST', 'path': '/api/parlay/grade',
                'body': '{legs: [...]}'
            },
            'synergy_team': {
                'method': 'GET',
                'path': '/api/synergy/team-playtypes/{team_id}'
            },
            'evolution_detect': {
                'method': 'POST', 'path': '/api/evolution/detect-changepoints',
                'body': '{values, min_size?, penalty?, player_id?, metric?}'
            },
            'api_docs': {'method': 'GET', 'path': '/api/docs'},
            # Supplement endpoints
            'coaching_tendencies': {
                'method': 'GET',
                'path': '/api/coaching/tendencies/{coach_id}?sport={sport}'
            },
            'coaching_shift': {
                'method': 'GET',
                'path': '/api/coaching/shift-detection/{team_id}?sport={sport}'
            },
            'redistribution': {
                'method': 'GET',
                'path': '/api/redistribution/calculate/{player_out_id}/{game_id}'
            },
            'alt_lines': {
                'method': 'GET',
                'path': '/api/odds/alt-lines/{sport}/{player_name}/{stat_type}'
            },
            'evolution_scan': {
                'method': 'GET',
                'path': '/api/evolution/scan/{sport}'
            },
            'unconventional_status': {
                'method': 'GET',
                'path': '/api/unconventional/status'
            },
            'unconventional_validate': {
                'method': 'POST',
                'path': '/api/unconventional/validate/{factor_name}'
            }
        },
        'version': '5.1',
        'shadow_mode': SHADOW_MODE
    })


# --- Cold Start Boot Sequence ---

def cold_start_boot():
    """
    Day-one initialization. Order matters — later steps depend on earlier ones.
    Called once on startup. Non-fatal failures are logged but don't block boot.
    """
    logger.info('[VYNDR] Cold start boot sequence initiated')

    # Load static data files
    data_dir = os.path.join(os.path.dirname(__file__), 'data')
    _load_json(os.path.join(data_dir, 'park_factors.json'), 'park_factors')
    _load_json(os.path.join(data_dir, 'reporter_database.json'), 'reporter_database')
    _load_json(os.path.join(data_dir, 'timezone_map.json'), 'timezone_map')
    _load_json(os.path.join(data_dir, 'grade_thresholds.json'), 'grade_thresholds')

    # Seed reporter database into Supabase reporter_trust table
    try:
        _seed_reporter_database(data_dir)
    except Exception as e:
        logger.warning(f'[VYNDR] Reporter seeding skipped: {e}')

    logger.info('[VYNDR] Cold start complete — engine ready to grade')


def _load_json(path, name):
    """Load a JSON data file. Log warning if missing."""
    try:
        with open(path) as f:
            data = json.load(f)
            logger.info(f'[VYNDR] Loaded {name} ({len(str(data))} bytes)')
            return data
    except FileNotFoundError:
        logger.warning(f'[VYNDR] Data file not found: {path}')
        return None
    except json.JSONDecodeError as e:
        logger.error(f'[VYNDR] Invalid JSON in {path}: {e}')
        return None


def _seed_reporter_database(data_dir):
    """
    Populate reporter_trust table from reporter_database.json.
    Each reporter gets a starting trust tier based on their source_type.
    Beat writers start at 'reliable'. Nationals start at 'authoritative'.
    Aggregators start at 'unverified'.
    """
    STARTING_TRUST = {
        'beat_writer': 'reliable',
        'national': 'authoritative',
        'insider': 'reliable',
        'aggregator': 'unverified'
    }

    path = os.path.join(data_dir, 'reporter_database.json')
    try:
        with open(path) as f:
            reporters = json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        logger.warning('[VYNDR] Reporter database not found for seeding')
        return

    from utils.supabase_client import get_supabase_client
    supabase = get_supabase_client()
    if not supabase:
        logger.warning('[VYNDR] Supabase not available — reporter seeding skipped')
        return

    count = 0
    for sport, teams in reporters.items():
        if not isinstance(teams, dict):
            continue
        for team_id, team_reporters in teams.items():
            if not isinstance(team_reporters, list):
                continue
            for reporter in team_reporters:
                source_type = reporter.get('source_type', 'beat_writer')
                starting_trust = STARTING_TRUST.get(source_type, 'unverified')
                try:
                    supabase.table('reporter_trust').upsert({
                        'handle': reporter['handle'],
                        'sport': sport,
                        'team_id': team_id,
                        'outlet': reporter.get('outlet', ''),
                        'source_type': source_type,
                        'trust_level': starting_trust,
                        'starting_trust': starting_trust
                    }, on_conflict='handle').execute()
                    count += 1
                except Exception as e:
                    logger.warning(f'[VYNDR] Failed to seed reporter {reporter.get("handle")}: {e}')

    logger.info(f'[VYNDR] Seeded {count} reporters into reporter_trust')


# --- Main ---

if __name__ == '__main__':
    cold_start_boot()
    port = int(os.environ.get('PORT', 5001))
    logger.info(f'[VYNDR] Starting Flask app on port {port}')
    app.run(host='0.0.0.0', port=port, debug=False)
