"""
VYNDR Lineup Intelligence — Multi-source lineup monitoring.
Blueprint providing real-time lineup status by aggregating official APIs,
beat reporter tweets, and backup sources. Tracks reporter accuracy over time
and promotes/demotes trust tiers dynamically.
"""

import logging
import re
from datetime import datetime, date

from flask import Blueprint, request, jsonify

from utils.data_warehouse import fetch_with_cache
from utils.retry import api_call_with_retry

logger = logging.getLogger('vyndr')
lineup_bp = Blueprint('lineup_intelligence', __name__)

# ---------------------------------------------------------------------------
# Source priority configuration
# ---------------------------------------------------------------------------

LINEUP_SOURCES = {
    'official_api': {
        'priority': 1,
        'description': 'Official league API (MLB statsapi, NBA official)',
        'badge_on_confirm': 'confirmed',
    },
    'beat_reporter': {
        'priority': 2,
        'description': 'Beat reporters and insiders — trust is dynamic',
        'badge_on_confirm': 'preliminary',
    },
    'backup_api': {
        'priority': 3,
        'description': 'Fallback third-party data feeds',
        'badge_on_confirm': 'preliminary',
    },
}

# ---------------------------------------------------------------------------
# Reporter trust system
# ---------------------------------------------------------------------------

REPORTER_TRUST_TIERS = {
    'unverified': {
        'min_tracked': 0,
        'accuracy': 0.0,
        'badge': 'preliminary',
    },
    'reliable': {
        'min_tracked': 10,
        'accuracy': 0.80,
        'badge': 'preliminary',
    },
    'verified': {
        'min_tracked': 20,
        'accuracy': 0.90,
        'badge': 'high_confidence',
    },
    'authoritative': {
        'min_tracked': 30,
        'accuracy': 0.95,
        'badge': 'confirmed',
    },
}

STARTING_TRUST = {
    'beat_writer': 'reliable',
    'national': 'authoritative',
    'insider': 'reliable',
    'aggregator': 'unverified',
}

# In-memory reporter tracking — production would persist to Supabase.
_reporter_stats = {}

# In-memory lineup cache keyed by (sport, game_date, game_id).
_lineup_cache = {}

# In-memory reporter-to-line-movement correlation log.
_reporter_line_correlations = []

# ---------------------------------------------------------------------------
# Tweet parsing
# ---------------------------------------------------------------------------

LINEUP_KEYWORDS = {
    'confirmed_playing': [
        'will play', 'starting', 'in the lineup', 'cleared to play',
        'available tonight', 'is a go', 'will start', 'expected to play',
        'in tonight', 'active tonight',
    ],
    'scratched': [
        'scratched', 'out tonight', 'will not play', 'ruled out',
        'sits tonight', 'will miss', 'inactive', 'dnp', 'is out',
        'not in lineup', 'held out',
    ],
    'questionable': [
        'questionable', 'game-time decision', 'gtd', 'uncertain',
        'doubtful', 'may sit', 'TBD', 'monitor', 'day-to-day',
        'not certain',
    ],
}

PAST_TENSE_FILTERS = [
    'played', 'started', 'was scratched', 'sat out', 'missed',
    'did not play', 'was ruled out', 'was inactive', 'had',
    'finished', 'went for', 'scored', 'posted',
]


def parse_reporter_tweet(tweet_text, tweet_date=None):
    """
    Parse a reporter tweet for lineup-relevant information.

    Filters out past-tense recaps and tweets that do not reference today's
    games. Returns a dict with player mentions, detected status, and the
    raw keyword match, or None if the tweet is not actionable.

    Args:
        tweet_text: Raw text content of the tweet.
        tweet_date: Date the tweet was posted (datetime.date). Defaults to
                    today if not provided.

    Returns:
        dict with keys {status, keywords_matched, raw_text} or None.
    """
    if tweet_date is None:
        tweet_date = date.today()

    if tweet_date != date.today():
        logger.debug('Skipping tweet from non-today date: %s', tweet_date)
        return None

    lower = tweet_text.lower()

    # Filter past-tense recaps
    for phrase in PAST_TENSE_FILTERS:
        if phrase in lower:
            logger.debug('Filtered past-tense tweet: %s', tweet_text[:80])
            return None

    # Detect lineup status keywords
    for status, keywords in LINEUP_KEYWORDS.items():
        matched = [kw for kw in keywords if kw.lower() in lower]
        if matched:
            return {
                'status': status,
                'keywords_matched': matched,
                'raw_text': tweet_text,
            }

    return None


# ---------------------------------------------------------------------------
# Reporter trust management
# ---------------------------------------------------------------------------

def _get_reporter_record(reporter_handle):
    """
    Retrieve or initialize the tracking record for a reporter.

    Args:
        reporter_handle: Twitter/X handle of the reporter.

    Returns:
        dict with keys {handle, total, correct, tier}.
    """
    if reporter_handle not in _reporter_stats:
        _reporter_stats[reporter_handle] = {
            'handle': reporter_handle,
            'total': 0,
            'correct': 0,
            'tier': 'unverified',
        }
    return _reporter_stats[reporter_handle]


def update_reporter_trust(reporter_handle, was_correct):
    """
    Update a reporter's accuracy tracking and promote/demote their tier.

    Called after an official source confirms or contradicts a reporter's
    earlier lineup call. Walks through REPORTER_TRUST_TIERS from highest
    to lowest and assigns the best tier the reporter qualifies for.

    Args:
        reporter_handle: Twitter/X handle of the reporter.
        was_correct: Boolean — did the official source confirm the call?

    Returns:
        dict with {handle, tier, accuracy, total}.
    """
    record = _get_reporter_record(reporter_handle)
    record['total'] += 1
    if was_correct:
        record['correct'] += 1

    accuracy = record['correct'] / record['total'] if record['total'] > 0 else 0.0

    # Walk tiers from best to worst, assign the highest that qualifies.
    tier_order = ['authoritative', 'verified', 'reliable', 'unverified']
    assigned_tier = 'unverified'
    for tier_name in tier_order:
        tier_def = REPORTER_TRUST_TIERS[tier_name]
        if (record['total'] >= tier_def['min_tracked']
                and accuracy >= tier_def['accuracy']):
            assigned_tier = tier_name
            break

    record['tier'] = assigned_tier
    logger.info(
        'Reporter %s updated: tier=%s accuracy=%.2f total=%d',
        reporter_handle, assigned_tier, accuracy, record['total'],
    )

    return {
        'handle': reporter_handle,
        'tier': assigned_tier,
        'accuracy': round(accuracy, 4),
        'total': record['total'],
    }


def get_reporter_badge(reporter_handle):
    """
    Return the display badge for a reporter based on their current trust tier.

    The badge maps directly from REPORTER_TRUST_TIERS and controls how the
    frontend labels lineup intel sourced from this reporter.

    Args:
        reporter_handle: Twitter/X handle of the reporter.

    Returns:
        str badge value (e.g. 'preliminary', 'high_confidence', 'confirmed').
    """
    record = _get_reporter_record(reporter_handle)
    tier = record.get('tier', 'unverified')
    return REPORTER_TRUST_TIERS.get(tier, REPORTER_TRUST_TIERS['unverified'])['badge']


# ---------------------------------------------------------------------------
# Two-stage lineup grading
# ---------------------------------------------------------------------------

def process_lineup_update(game_id, sport, player_name, status, source_type,
                          reporter_handle=None):
    """
    Two-stage lineup grading pipeline.

    Stage 1 (beat_reporter / backup_api): Record the update with a
    preliminary badge. The confidence depends on the reporter's trust tier.

    Stage 2 (official_api): Stamp the update with a confirmed badge and
    back-validate any earlier reporter calls for that player/game.

    Args:
        game_id: Unique identifier for the game.
        sport: Sport key (e.g. 'mlb', 'nba').
        player_name: Full player name.
        status: One of 'confirmed_playing', 'scratched', 'questionable'.
        source_type: Key from LINEUP_SOURCES ('official_api', 'beat_reporter',
                     'backup_api').
        reporter_handle: Required when source_type is 'beat_reporter'.

    Returns:
        dict with the stored lineup entry including badge and timestamp.
    """
    cache_key = (sport, game_id, player_name.lower())
    now = datetime.utcnow().isoformat()

    source_def = LINEUP_SOURCES.get(source_type)
    if source_def is None:
        logger.error('Unknown source_type: %s', source_type)
        return {'error': f'Unknown source_type: {source_type}'}

    # Determine badge
    if source_type == 'official_api':
        badge = 'confirmed'
    elif source_type == 'beat_reporter' and reporter_handle:
        badge = get_reporter_badge(reporter_handle)
    else:
        badge = source_def.get('badge_on_confirm', 'preliminary')

    entry = {
        'game_id': game_id,
        'sport': sport,
        'player': player_name,
        'status': status,
        'source': source_type,
        'reporter': reporter_handle,
        'badge': badge,
        'timestamp': now,
    }

    existing = _lineup_cache.get(cache_key)

    # Stage 2: official confirmation — back-validate reporter calls.
    if source_type == 'official_api' and existing:
        prior_source = existing.get('source')
        prior_reporter = existing.get('reporter')
        if prior_source == 'beat_reporter' and prior_reporter:
            was_correct = existing.get('status') == status
            update_reporter_trust(prior_reporter, was_correct)
            logger.info(
                'Back-validated reporter %s for %s: correct=%s',
                prior_reporter, player_name, was_correct,
            )

    # Only overwrite if the new source has equal or higher priority.
    if existing is None or source_def['priority'] <= LINEUP_SOURCES.get(
            existing.get('source', ''), {}).get('priority', 99):
        _lineup_cache[cache_key] = entry
        logger.info(
            'Lineup update stored: %s %s -> %s [%s]',
            player_name, status, source_type, badge,
        )
    else:
        logger.debug(
            'Skipped lower-priority update for %s from %s',
            player_name, source_type,
        )

    return entry


# ---------------------------------------------------------------------------
# PATCH: Scratch → Redistribution → Re-grade → Alt Line → Alert chain
# ---------------------------------------------------------------------------

def handle_scratch_chain(player_name, player_id, team, game_id, sport, badge):
    """
    Full chain when a player is confirmed OUT:
    1. Trigger redistribution engine for absorption analysis
    2. Re-grade affected props with redistribution context
    3. Auto-scan alt lines for any A-grade re-grades
    4. Format and return alert with all intelligence

    Args:
        player_name: Scratched player name.
        player_id: Scratched player ID.
        team: Team identifier.
        game_id: Game identifier.
        sport: 'nba' or 'mlb'.
        badge: Reporter badge level.

    Returns:
        Dict with redistribution, re-graded props, and alt line opportunities.
    """
    result = {
        'player_scratched': player_name,
        'redistribution': None,
        'regraded_props': [],
        'alt_opportunities': [],
        'alert': None
    }

    # Step 1: Redistribution
    try:
        from blueprints.redistribution import calculate_redistribution_internal
        redistribution = calculate_redistribution_internal(player_id, game_id)
        result['redistribution'] = redistribution
    except Exception as e:
        logger.warning(f'[VYNDR] Redistribution chain failed: {e}')
        redistribution = None

    # Step 2: Re-grade affected props (stub — connects to grading engine)
    # In production, get_props_affected_by_scratch returns live props
    # and recalculate_grade runs the full pipeline with redistribution_context

    # Step 3: Alt line scan for A-grade re-grades
    try:
        from blueprints.odds_scanner import scan_alt_lines_internal
        for prop in result.get('regraded_props', []):
            if prop.get('grade') in ['A+', 'A', 'A-']:
                alt = scan_alt_lines_internal(
                    sport, prop.get('player', ''),
                    prop.get('stat_type', ''),
                    standard_grade=prop
                )
                if alt.get('recommend_alt'):
                    result['alt_opportunities'].append(alt)
                    prop['alt_line_opportunity'] = alt.get('optimal_alt')
    except Exception as e:
        logger.warning(f'[VYNDR] Alt line chain failed: {e}')

    # Step 4: Format alert
    if redistribution and redistribution.get('primary_beneficiary'):
        primary = redistribution['primary_beneficiary']
        alert = (
            f"{player_name} is OUT.\n"
            f"{primary.get('player_name', '?')} is underpriced. "
            f"Boost: +{primary.get('combined_prop_boost', 0):.0%}. "
            f"Confidence: {primary.get('confidence', 0):.0%}."
        )
        if result['alt_opportunities']:
            alt = result['alt_opportunities'][0].get('optimal_alt', {})
            alert += (
                f"\n\nAlt line: {alt.get('over_under', '').upper()} "
                f"{alt.get('line', '?')} at {alt.get('odds', '?')} "
                f"\u2192 Edge: {alt.get('real_edge', 0):.1%}"
            )
        result['alert'] = alert

    return result


def poll_reporter_feeds(sport):
    """
    Poll reporter feeds for lineup updates. Called by GitHub Actions cron.

    Args:
        sport: 'nba' or 'mlb'.
    """
    logger.info(f'[VYNDR] Polling reporter feeds for {sport}')
    # In production, fetch from Twitter API / RSS feeds
    # Parse via parse_reporter_tweet, process via process_lineup_update


def check_all_lineups(sport):
    """
    Check all lineup statuses from official APIs. Called by pre-game cron.

    Args:
        sport: 'nba' or 'mlb'.
    """
    logger.info(f'[VYNDR] Checking all lineups for {sport}')
    # In production, fetch from official MLB/NBA APIs


# ---------------------------------------------------------------------------
# Reporter-to-line-movement correlation
# ---------------------------------------------------------------------------

def log_reporter_line_correlation(reporter_handle, game_id, player_name,
                                  tweet_timestamp, line_move_timestamp,
                                  line_before, line_after):
    """
    Track the time gap between a reporter's tweet and subsequent book line
    movement. Used to measure how quickly the market prices reporter intel.

    Args:
        reporter_handle: Twitter/X handle.
        game_id: Unique game identifier.
        player_name: Player referenced in the tweet.
        tweet_timestamp: ISO timestamp of the tweet.
        line_move_timestamp: ISO timestamp of the detected line move.
        line_before: Odds/line value before the move.
        line_after: Odds/line value after the move.

    Returns:
        dict with the correlation record including gap_seconds.
    """
    try:
        tweet_dt = datetime.fromisoformat(tweet_timestamp)
        move_dt = datetime.fromisoformat(line_move_timestamp)
        gap_seconds = (move_dt - tweet_dt).total_seconds()
    except (ValueError, TypeError) as exc:
        logger.warning('Could not compute gap for %s: %s', reporter_handle, exc)
        gap_seconds = None

    record = {
        'reporter': reporter_handle,
        'game_id': game_id,
        'player': player_name,
        'tweet_timestamp': tweet_timestamp,
        'line_move_timestamp': line_move_timestamp,
        'line_before': line_before,
        'line_after': line_after,
        'gap_seconds': gap_seconds,
    }

    _reporter_line_correlations.append(record)
    logger.info(
        'Line correlation logged: reporter=%s player=%s gap=%.1fs line %s->%s',
        reporter_handle, player_name,
        gap_seconds if gap_seconds is not None else -1,
        line_before, line_after,
    )

    return record


# ---------------------------------------------------------------------------
# MLB lineup parsing via statsapi
# ---------------------------------------------------------------------------

def get_mlb_lineups_today(game_date=None):
    """
    Fetch today's MLB starting lineups from the official statsapi.

    Uses utils.retry for resilience and utils.data_warehouse for caching
    (15-minute TTL since lineups can change close to game time).

    Args:
        game_date: Date string in 'YYYY-MM-DD' format. Defaults to today.

    Returns:
        list of dicts, one per game, each containing home/away lineup arrays.
    """
    if game_date is None:
        game_date = date.today().strftime('%Y-%m-%d')

    cache_key = f'mlb_lineups_{game_date}'

    def _fetch():
        """Inner fetch wrapped for retry and caching."""
        try:
            import statsapi
        except ImportError:
            logger.error('statsapi not installed — cannot fetch MLB lineups')
            return []

        schedule = api_call_with_retry(
            lambda: statsapi.schedule(date=game_date),
            max_retries=3,
            label='statsapi.schedule',
        )

        if not schedule:
            logger.warning('No MLB games found for %s', game_date)
            return []

        games = []
        for game in schedule:
            game_id = game.get('game_id')
            if game_id is None:
                continue

            try:
                boxscore = api_call_with_retry(
                    lambda gid=game_id: statsapi.boxscore_data(gid),
                    max_retries=3,
                    label='statsapi.boxscore_data',
                )
            except Exception as exc:
                logger.warning('Failed to get boxscore for game %s: %s', game_id, exc)
                continue

            home_lineup = []
            away_lineup = []

            for side, lineup_list in [('home', home_lineup), ('away', away_lineup)]:
                batters_key = f'{side}Batters'
                batters = boxscore.get(batters_key, [])
                for batter in batters:
                    if isinstance(batter, dict):
                        name = batter.get('name', batter.get('namefield', ''))
                        if name:
                            lineup_list.append({
                                'name': name.strip(),
                                'position': batter.get('position', ''),
                                'batting_order': batter.get('battingOrder', ''),
                            })

            games.append({
                'game_id': game_id,
                'home_team': game.get('home_name', ''),
                'away_team': game.get('away_name', ''),
                'game_time': game.get('game_datetime', ''),
                'status': game.get('status', ''),
                'home_lineup': home_lineup,
                'away_lineup': away_lineup,
            })

        logger.info('Fetched %d MLB game lineups for %s', len(games), game_date)
        return games

    return fetch_with_cache(cache_key, _fetch, ttl=900)


# ---------------------------------------------------------------------------
# Routes
# ---------------------------------------------------------------------------

@lineup_bp.route('/status/<sport>/<game_date>', methods=['GET'])
def lineup_status(sport, game_date):
    """
    Return lineup status for all tracked games in a sport on a given date.

    Pulls from the in-memory lineup cache and, for MLB, supplements with
    official statsapi data. Results include the confidence badge for each
    player entry.

    Args:
        sport: Sport key ('mlb', 'nba', 'nfl', etc.).
        game_date: Date string 'YYYY-MM-DD'.

    Returns:
        JSON response with lineup entries grouped by game.
    """
    try:
        target_date = datetime.strptime(game_date, '%Y-%m-%d').date()
    except ValueError:
        return jsonify({'error': 'Invalid date format. Use YYYY-MM-DD.'}), 400

    # Gather cached entries for the sport/date
    entries = []
    for (cached_sport, cached_game, _player), entry in _lineup_cache.items():
        if cached_sport == sport:
            entries.append(entry)

    # For MLB, supplement with official lineups if available
    if sport == 'mlb':
        try:
            official_lineups = get_mlb_lineups_today(game_date)
            for game in official_lineups:
                for side in ['home_lineup', 'away_lineup']:
                    for player in game.get(side, []):
                        process_lineup_update(
                            game_id=str(game['game_id']),
                            sport='mlb',
                            player_name=player['name'],
                            status='confirmed_playing',
                            source_type='official_api',
                        )
        except Exception as exc:
            logger.warning('MLB official lineup fetch failed: %s', exc)

    # Re-gather after potential official update
    result = {}
    for (cached_sport, cached_game, _player), entry in _lineup_cache.items():
        if cached_sport == sport:
            result.setdefault(cached_game, []).append(entry)

    return jsonify({
        'sport': sport,
        'date': game_date,
        'games': result,
        'total_entries': sum(len(v) for v in result.values()),
    })


@lineup_bp.route('/reporter-update', methods=['POST'])
def reporter_update():
    """
    Process a reporter tweet and store the lineup update.

    Expects JSON body:
        {
            "reporter_handle": "@handle",
            "reporter_type": "beat_writer" | "national" | "insider" | "aggregator",
            "tweet_text": "Player X will play tonight...",
            "tweet_date": "YYYY-MM-DD" (optional, defaults to today),
            "game_id": "game_123",
            "sport": "mlb",
            "player_name": "Player X"
        }

    Returns:
        JSON with the parsed tweet result and stored lineup entry, or an
        error if the tweet was filtered or unparseable.
    """
    data = request.get_json(silent=True)
    if not data:
        return jsonify({'error': 'Request body must be JSON.'}), 400

    required = ['reporter_handle', 'tweet_text', 'game_id', 'sport', 'player_name']
    missing = [f for f in required if f not in data]
    if missing:
        return jsonify({'error': f'Missing required fields: {missing}'}), 400

    reporter_handle = data['reporter_handle']
    reporter_type = data.get('reporter_type', 'aggregator')
    tweet_text = data['tweet_text']
    game_id = data['game_id']
    sport = data['sport']
    player_name = data['player_name']

    # Parse tweet date
    tweet_date = None
    if data.get('tweet_date'):
        try:
            tweet_date = datetime.strptime(data['tweet_date'], '%Y-%m-%d').date()
        except ValueError:
            return jsonify({'error': 'Invalid tweet_date format. Use YYYY-MM-DD.'}), 400

    # Initialize reporter trust if first time seeing them
    record = _get_reporter_record(reporter_handle)
    if record['total'] == 0 and reporter_type in STARTING_TRUST:
        record['tier'] = STARTING_TRUST[reporter_type]

    # Parse the tweet
    parsed = parse_reporter_tweet(tweet_text, tweet_date=tweet_date)
    if parsed is None:
        return jsonify({
            'filtered': True,
            'reason': 'Tweet filtered (past tense, non-today, or no lineup keywords).',
        }), 200

    # Store lineup update
    entry = process_lineup_update(
        game_id=game_id,
        sport=sport,
        player_name=player_name,
        status=parsed['status'],
        source_type='beat_reporter',
        reporter_handle=reporter_handle,
    )

    return jsonify({
        'filtered': False,
        'parsed': parsed,
        'lineup_entry': entry,
        'reporter_badge': get_reporter_badge(reporter_handle),
    })
