"""
Core eligibility engine: determines prop publish eligibility.

Orchestrates all validation checks and produces a final ValidationState.
"""

import logging
from datetime import datetime, timezone
from typing import List, Dict, Any, Optional

from .enums import ValidationState, AvailabilityStatus, LineupStatus, AuditAction
from .config import get_policy, REQUIRE_MARKET_CONFIRMATION
from .normalization import (
    canonicalize_team_identifier,
    extract_player_canonical_id,
    normalize_player_name,
    normalize_team_name,
)
from .availability import validate_player_availability
from .lineup_validation import validate_lineup_status, is_lineup_dependent
from .reconciliation import reconcile_player_team
from .market_ingest import check_market_presence
from .models import PropEligibility, ConflictDetail

log = logging.getLogger("pub-integrity")


def _build_game_team_ids(
    league: str,
    home_team: str,
    away_team: str,
    event_id: str,
) -> set[str]:
    team_ids: set[str] = set()

    for team_name in (home_team, away_team):
        canonical = canonicalize_team_identifier(team_name, league)
        if canonical:
            team_ids.add(canonical)

    parts = [part for part in str(event_id or "").split("-") if part]
    for token in parts[1:-1]:
        canonical = canonicalize_team_identifier(token, league)
        if canonical:
            team_ids.add(canonical)

    return team_ids


def determine_prop_eligibility(
    player_name: str,
    prop_type: str,
    event_id: str,
    league: str,
    home_team: str,
    away_team: str,
    game_start: Optional[datetime],
    run_id: str,
    roster_entries: List[Dict[str, Any]],
    availability_reports: List[Dict[str, Any]],
    lineup_data: List[Dict[str, Any]],
    market_props: List[Dict[str, Any]],
) -> PropEligibility:
    """
    Run all validation checks for a single player prop.
    Returns a PropEligibility with the final validation state.
    """
    policy = get_policy(league)
    norm_name = normalize_player_name(player_name)
    canonical_id = extract_player_canonical_id(player_name, league)
    now = datetime.now(timezone.utc)

    elig = PropEligibility(
        run_id=run_id,
        game_id=event_id,
        player_id=canonical_id,
        player_name=player_name,
        team_id="",
        league=league,
    )

    # ── Gate 1: Canonical ID resolution ──────────────────────────────────
    if not norm_name or len(norm_name) < 2:
        elig.validation_state = ValidationState.SUPPRESSED_NO_CANONICAL_ID
        elig.suppress_reason = "SUPPRESSED_NO_CANONICAL_ID"
        elig.suppress_reason_human = f"Cannot resolve canonical ID for '{player_name}'"
        elig.publish_allowed = False
        return elig

    # Determine which team the prop is for (infer from event)
    prop_team = ""
    for roster in roster_entries:
        if roster.get("normalized_name") == norm_name:
            prop_team = roster.get("team", "")
            break

    elig.team_id = prop_team

    # ── Gate 2a: Player's team must be one of the teams in this game ─────
    if prop_team:
        # Check if player's roster team matches either game team
        prop_team_upper = canonicalize_team_identifier(prop_team, league)
        game_team_ids = _build_game_team_ids(league, home_team, away_team, event_id)
        team_in_game = not prop_team_upper or prop_team_upper in game_team_ids

        if not team_in_game:
            conflict = ConflictDetail(
                conflict_type="PLAYER_WRONG_GAME",
                player_name=player_name,
                player_id=norm_name,
                field="game_team",
                official_value=prop_team,
                internal_value=f"{home_team} vs {away_team}",
                severity="high",
                source="game_team_check",
                detail=f"Player {player_name} is on {prop_team} but game is "
                       f"{away_team} @ {home_team}",
            )
            elig.validation_state = ValidationState.SUPPRESSED_TEAM_MISMATCH
            elig.suppress_reason = "SUPPRESSED_TEAM_MISMATCH"
            elig.suppress_reason_human = conflict.detail
            elig.conflict_payload = conflict.to_dict()
            elig.publish_allowed = False
            elig.roster_verified_at = now
            return elig

    # ── Gate 2b: Team reconciliation (roster vs market) ──────────────────
    if prop_team:
        team_valid, team_conflict = reconcile_player_team(
            player_name, prop_team, roster_entries, market_props, league
        )
        if not team_valid and team_conflict:
            elig.validation_state = ValidationState.SUPPRESSED_TEAM_MISMATCH
            elig.suppress_reason = "SUPPRESSED_TEAM_MISMATCH"
            elig.suppress_reason_human = team_conflict.detail
            elig.conflict_payload = team_conflict.to_dict()
            elig.publish_allowed = False
            elig.roster_verified_at = now
            return elig

    elig.roster_verified_at = now

    # ── Gate 3: Availability check ───────────────────────────────────────
    avail_status, avail_conflict = validate_player_availability(
        player_name, availability_reports, policy.blocking_statuses
    )

    if avail_status.is_blocking:
        elig.validation_state = ValidationState.SUPPRESSED_UNAVAILABLE
        elig.suppress_reason = "SUPPRESSED_UNAVAILABLE"
        elig.suppress_reason_human = (avail_conflict.detail if avail_conflict
                                       else f"Player {player_name} is {avail_status.value}")
        if avail_conflict:
            elig.conflict_payload = avail_conflict.to_dict()
        elig.publish_allowed = False
        elig.availability_verified_at = now
        return elig

    elig.availability_verified_at = now

    # ── Gate 4: Lineup/starter validation ────────────────────────────────
    lineup_status, lineup_conflict = validate_lineup_status(
        player_name, prop_type, league, game_start, lineup_data
    )

    if lineup_conflict:
        elig.validation_state = ValidationState.SUPPRESSED_NOT_IN_STARTING_LINEUP
        elig.suppress_reason = "SUPPRESSED_NOT_IN_STARTING_LINEUP"
        elig.suppress_reason_human = lineup_conflict.detail
        elig.conflict_payload = lineup_conflict.to_dict()
        elig.publish_allowed = False
        elig.lineup_verified_at = now
        return elig

    # If lineup-dependent but lineup unknown, mark as pending
    if is_lineup_dependent(prop_type, league) and not lineup_status.is_confirmed_starter:
        if lineup_status == LineupStatus.UNKNOWN:
            elig.validation_state = ValidationState.VERIFIED_PENDING_LINEUP
            elig.publish_allowed = policy.allow_pre_lineup_props
            elig.lineup_verified_at = now
            # Continue — don't return yet, still check market
        else:
            elig.lineup_verified_at = now
    else:
        elig.lineup_verified_at = now

    # ── Gate 5: Market confirmation (optional) ───────────────────────────
    has_market = check_market_presence(market_props, player_name)
    elig.market_presence_flag = has_market

    if REQUIRE_MARKET_CONFIRMATION and policy.require_market_presence and not has_market:
        elig.validation_state = ValidationState.SUPPRESSED_NO_MARKET_CONFIRMATION
        elig.suppress_reason = "SUPPRESSED_NO_MARKET_CONFIRMATION"
        elig.suppress_reason_human = (
            f"No sportsbook market found for {player_name} — "
            f"league policy requires market confirmation"
        )
        elig.publish_allowed = False
        return elig

    # ── All gates passed ─────────────────────────────────────────────────
    if elig.validation_state != ValidationState.VERIFIED_PENDING_LINEUP:
        elig.validation_state = ValidationState.VERIFIED
        elig.publish_allowed = True

    elig.winning_source = "all_gates_passed"
    return elig
