"""
VYNDR Auto-Calibration Engine
Point-biserial correlation for weight calibration.
Global offset. Brier score tracking. Blind spot detection.
"""

import logging
from datetime import datetime
from flask import Blueprint, request, jsonify

from utils.bayesian import (
    calculate_global_offset, calculate_brier_score, GRADE_THRESHOLDS
)
from utils.blind_spot_detector import detect_model_blind_spots, track_catastrophic_misses

logger = logging.getLogger('vyndr')
calibration_bp = Blueprint('calibration', __name__)

# Calibration thresholds
PLAYER_CALIBRATION_THRESHOLDS = [25, 50, 75, 100]
GLOBAL_OFFSET_THRESHOLDS = [100, 250, 500, 1000]
POINT_BISERIAL_BOUNDS = {'min': 0.05, 'max': 0.50}


def calibrate_weights(player_id, sport, stat_type, outcomes, min_sample=25):
    """
    Calibrate per-player weights using point-biserial correlation.
    Bounds each weight between 0.05 and 0.50. Triggers at 25/50/75/100 resolved.

    Args:
        player_id: Player identifier.
        sport: 'nba' or 'mlb'.
        stat_type: Stat type string.
        outcomes: List of resolved outcome dicts with 'hit' and 'sub_scores'.
        min_sample: Minimum sample size (default 25).

    Returns:
        Dict of calibrated weights, or None if insufficient data.
    """
    if len(outcomes) < min_sample:
        return None

    try:
        from scipy.stats import pointbiserialr
    except ImportError:
        logger.warning('[VYNDR] scipy not available for calibration')
        return None

    hits = [1 if o['hit'] else 0 for o in outcomes]
    sub_score_keys = list(outcomes[0].get('sub_scores', {}).keys())

    if not sub_score_keys:
        return None

    correlations = {}
    for key in sub_score_keys:
        scores = [o.get('sub_scores', {}).get(key, 0.5) for o in outcomes]
        try:
            corr, p_value = pointbiserialr(hits, scores)
            # Only use correlation if p < 0.10, otherwise use minimum bound
            correlations[key] = abs(corr) if p_value < 0.10 else POINT_BISERIAL_BOUNDS['min']
        except Exception:
            correlations[key] = POINT_BISERIAL_BOUNDS['min']

    # Clamp to bounds and normalize
    clamped = {
        k: max(POINT_BISERIAL_BOUNDS['min'], min(POINT_BISERIAL_BOUNDS['max'], v))
        for k, v in correlations.items()
    }
    total = sum(clamped.values())
    if total == 0:
        return None

    new_weights = {k: round(v / total, 4) for k, v in clamped.items()}

    logger.info(
        f'[VYNDR] Calibrated weights for {player_id}/{sport}/{stat_type} '
        f'(n={len(outcomes)}): {new_weights}'
    )
    return new_weights


# --- Endpoints ---

@calibration_bp.route('/weights/<player_id>', methods=['GET'])
def get_player_weights(player_id):
    """
    Get calibrated weights for a player, or defaults if not yet calibrated.

    Args:
        player_id: Player identifier.

    Query params:
        sport: 'nba' or 'mlb'.
        stat_type: Stat type string.

    Returns:
        JSON with weights, source ('calibrated' or 'default'), and sample_size.
    """
    sport = request.args.get('sport', 'nba')
    stat_type = request.args.get('stat_type', 'points')

    # In production, fetch from player_calibrated_weights table
    return jsonify({
        'player_id': player_id,
        'sport': sport,
        'stat_type': stat_type,
        'weights': None,
        'source': 'default',
        'sample_size': 0,
        'note': 'No calibrated weights yet — using archetype blend or defaults'
    })


@calibration_bp.route('/resolve/<game_date>', methods=['POST'])
def resolve_grades(game_date):
    """
    Trigger grade resolution for a specific date.
    Called by the nightly resolution pipeline.

    Args:
        game_date: Date string (YYYY-MM-DD).

    Returns:
        JSON with resolution summary.
    """
    # Delegate to resolution blueprint
    return jsonify({
        'game_date': game_date,
        'status': 'resolution_triggered',
        'note': 'Delegated to nightly resolution pipeline'
    })


@calibration_bp.route('/global-offset/<sport>', methods=['GET'])
def get_global_offset(sport):
    """
    Get the current global calibration offset for a sport.

    Args:
        sport: 'nba' or 'mlb'.

    Returns:
        JSON with offset_value, sample_size, calculated_at.
    """
    return jsonify({
        'sport': sport,
        'offset_value': 0.0,
        'sample_size': 0,
        'calculated_at': None,
        'note': 'No resolved grades yet — offset is 0.0'
    })


@calibration_bp.route('/brier-score/<sport>', methods=['GET'])
def get_brier_score(sport):
    """
    Get current Brier score for a sport.
    Lower is better. 0.0 = perfect. 0.25 = coin flip.

    Args:
        sport: 'nba' or 'mlb'.

    Returns:
        JSON with brier_score, sample_size, interpretation.
    """
    return jsonify({
        'sport': sport,
        'brier_score': None,
        'sample_size': 0,
        'interpretation': 'No resolved grades yet',
        'tracked_from': 'day_one'
    })


@calibration_bp.route('/blind-spots/<sport>', methods=['GET'])
def get_blind_spots(sport):
    """
    Get identified blind spots where the model underperforms.
    Only available after 200+ resolved grades.

    Args:
        sport: 'nba' or 'mlb'.

    Returns:
        JSON with blind_spots list and catastrophic_misses list.
    """
    return jsonify({
        'sport': sport,
        'blind_spots': [],
        'catastrophic_misses': [],
        'sample_size': 0,
        'minimum_required': 200,
        'note': 'Insufficient data for blind spot detection'
    })


@calibration_bp.route('/clv/<sport>', methods=['GET'])
def get_clv_report(sport):
    """
    Get Closing Line Value report for a sport.
    CLV measures whether the market moved toward our position.

    Args:
        sport: 'nba' or 'mlb'.

    Returns:
        JSON with CLV stats.
    """
    return jsonify({
        'sport': sport,
        'total_grades_with_clv': 0,
        'clv_win_rate': None,
        'avg_clv_magnitude': None,
        'note': 'CLV tracking begins when odds_warehouse has morning + pre-game data'
    })


@calibration_bp.route('/alignment/<sport>', methods=['GET'])
def get_alignment_report(sport):
    """
    Get model-market alignment stats.
    Shows how often the market moves WITH vs AGAINST VYNDR's position.

    Args:
        sport: 'nba' or 'mlb'.

    Returns:
        JSON with alignment stats.
    """
    return jsonify({
        'sport': sport,
        'confirming_count': 0,
        'contrarian_count': 0,
        'alignment_rate': None,
        'note': 'Alignment tracking begins with odds_warehouse data'
    })
