"""
Main orchestrator for Publishing Integrity validation runs.

Coordinates:
1. Morning full build
2. Midday confirmation
3. Pre-game final check
"""

import uuid
import json
import logging
import sys
from datetime import datetime
from typing import Dict, Any, List, Optional

import psycopg2
import psycopg2.extras

from .config import (
    is_league_enabled, PUBLISHING_INTEGRITY_ENABLED,
    get_policy, LEAGUE_FLAGS, LOG_DIR, LOG_FILE,
)
from .enums import RunType, ValidationState, AuditAction
from .official_ingest import (
    ingest_availability_from_injuries,
    ingest_rosters_from_pgm,
    ingest_today_games,
    ingest_forecast_props,
)
from .market_ingest import ingest_market_props, ingest_market_from_sgo_odds
from .reconciliation import (
    reconcile_player_team,
    detect_identity_collisions,
    detect_stale_mappings,
)
from .eligibility import determine_prop_eligibility
from .suppression import suppress_ineligible_props, restore_props_if_valid
from .repositories import (
    get_connection, PropEligibilityRepo, AuditLogRepo, RegressionRepo,
)
from .models import PropEligibility, RegressionFailure
from .audit import IntegrityAuditLogger
from .alerts import trigger_alert

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


def run_validation(
    run_type: RunType,
    leagues: Optional[List[str]] = None,
    dry_run: bool = False,
) -> Dict[str, Any]:
    """
    Execute a full validation pass.

    Args:
        run_type: morning_full, midday_confirm, or pre_game_final
        leagues: optional filter; defaults to all enabled leagues
        dry_run: if True, don't modify any data
    """
    if not PUBLISHING_INTEGRITY_ENABLED:
        log.info("Publishing integrity is DISABLED — skipping")
        return {"status": "disabled"}

    run_id = str(uuid.uuid4())[:12]
    audit = IntegrityAuditLogger(run_id, run_type.value)
    target_leagues = leagues or [l for l, enabled in LEAGUE_FLAGS.items() if enabled]

    log.info("=" * 60)
    log.info(f"PUBLISHING INTEGRITY RUN: {run_type.value} | run_id={run_id}")
    log.info(f"Leagues: {', '.join(target_leagues)} | dry_run={dry_run}")
    log.info("=" * 60)

    conn = get_connection()
    conn.autocommit = False
    all_stats: Dict[str, Any] = {}

    try:
        for league in target_leagues:
            if not is_league_enabled(league):
                continue

            league_stats = _validate_league(
                conn, league, run_id, run_type, audit, dry_run
            )
            all_stats[league] = league_stats

        if not dry_run:
            conn.commit()
        else:
            conn.rollback()

    except Exception:
        conn.rollback()
        log.exception("Validation run failed")
        raise
    finally:
        conn.close()

    summary = audit.summary()
    summary["league_stats"] = all_stats
    log.info(f"Run complete: {json.dumps(summary, indent=2, default=str)}")
    return summary


def _validate_league(
    conn,
    league: str,
    run_id: str,
    run_type: RunType,
    audit: IntegrityAuditLogger,
    dry_run: bool,
) -> Dict[str, Any]:
    """Validate all player props for a single league."""
    log.info(f"--- {league.upper()} ---")

    # Phase 1: Ingest official data
    availability_reports = ingest_availability_from_injuries(conn, league, run_id)
    roster_entries = ingest_rosters_from_pgm(conn, league, run_id)
    games = ingest_today_games(conn, league)
    forecast_props = ingest_forecast_props(conn, league)

    # Phase 2: Ingest market data (Tier 2 — sanity check only)
    market_props = ingest_market_props(conn, league, run_id)
    market_sgo = ingest_market_from_sgo_odds(conn, league, run_id)
    all_market = market_props + market_sgo

    # Phase 3: Cross-source reconciliation
    identity_conflicts = detect_identity_collisions(roster_entries, league)
    stale_conflicts = detect_stale_mappings(forecast_props, roster_entries)

    for conflict in identity_conflicts:
        audit.log_alert("high", f"Identity collision in {league}: {conflict.detail}")
        trigger_alert("identity_collision", conflict.to_dict())

    for conflict in stale_conflicts:
        audit.log_alert("medium", f"Stale mapping in {league}: {conflict.detail}")

    # Phase 4: Per-prop eligibility determination
    eligibilities: List[PropEligibility] = []
    identity_collision_names = {c.player_id for c in identity_conflicts}

    for prop in forecast_props:
        norm = prop.get("normalized_name", "")

        # Short-circuit identity collisions
        if norm in identity_collision_names:
            elig = PropEligibility(
                run_id=run_id,
                game_id=prop["event_id"],
                player_id=norm,
                player_name=prop["player_name"],
                team_id="",
                league=league,
                validation_state=ValidationState.SUPPRESSED_IDENTITY_COLLISION,
                suppress_reason="SUPPRESSED_IDENTITY_COLLISION",
                suppress_reason_human=f"Player '{prop['player_name']}' has identity collision — "
                                      f"appears on multiple teams",
                publish_allowed=False,
            )
        else:
            # Find game start time
            game_start = None
            for g in games:
                if g["event_id"] == prop["event_id"]:
                    sa = g.get("starts_at")
                    if sa:
                        game_start = sa if isinstance(sa, datetime) else datetime.fromisoformat(str(sa))
                    break

            elig = determine_prop_eligibility(
                player_name=prop["player_name"],
                prop_type=prop.get("prop_type", ""),
                event_id=prop["event_id"],
                league=league,
                home_team=prop.get("home_team", ""),
                away_team=prop.get("away_team", ""),
                game_start=game_start,
                run_id=run_id,
                roster_entries=roster_entries,
                availability_reports=availability_reports,
                lineup_data=[],  # Lineup data not yet available in morning/midday
                market_props=all_market,
            )

        # Persist eligibility
        PropEligibilityRepo.upsert(conn, elig)
        eligibilities.append(elig)

        # Audit
        action = AuditAction.SUPPRESSED if elig.validation_state.is_suppressed else AuditAction.PUBLISHED
        audit.log_decision(
            action=action,
            player_name=prop["player_name"],
            game_id=prop["event_id"],
            league=league,
            validation_state=elig.validation_state,
            suppress_reason=elig.suppress_reason,
            detail=elig.suppress_reason_human,
            conflict=elig.conflict_payload,
        )

    # Phase 5: Apply suppression
    suppress_stats = suppress_ineligible_props(conn, eligibilities, run_id, dry_run)

    # Phase 6: Restore previously-suppressed props that are now valid
    restored_count = 0
    if run_type in (RunType.MIDDAY_CONFIRM, RunType.PRE_GAME_FINAL):
        restored_count = restore_props_if_valid(conn, eligibilities, run_id)

    # Alerts for critical issues
    if suppress_stats["suppressed"] > 0:
        trigger_alert("props_suppressed", {
            "league": league,
            "count": suppress_stats["suppressed"],
            "run_id": run_id,
            "run_type": run_type.value,
        })

    league_stats = {
        "games": len(games),
        "forecast_props": len(forecast_props),
        **suppress_stats,
        "restored": restored_count,
        "identity_collisions": len(identity_conflicts),
        "stale_mappings": len(stale_conflicts),
    }

    log.info(f"[{league}] Props: {len(forecast_props)} | "
             f"Verified: {suppress_stats['verified']} | "
             f"Suppressed: {suppress_stats['suppressed']} | "
             f"Pending: {suppress_stats['pending_lineup']} | "
             f"Restored: {restored_count}")

    return league_stats
