import {
  americanOddsToImpliedProbability,
  buildPlayerPropSignal,
  getPlayerPropTypeLabel,
  normalizePlayerPropDirection,
} from './player-prop-signals';

import type {
  PublicFeaturedPlayer,
  PublicFeaturedPlayerProp,
} from '../contracts/forecast-public-shared';

type RawForecastRow = {
  id: string;
  player_name: string | null;
  team_id: string | null;
  team_side: 'home' | 'away' | null;
  league: string | null;
  confidence_score?: number | null;
  forecast_payload?: Record<string, any> | null;
  locked?: boolean;
  playerRole?: string | null;
};

type LineupPlayer = {
  name?: string | null;
  playerName?: string | null;
  fullName?: string | null;
};

export interface PlayerRadarLineupSnapshot {
  homeStatus?: string | null;
  awayStatus?: string | null;
  homePlayers?: LineupPlayer[] | null;
  awayPlayers?: LineupPlayer[] | null;
  homeProjMinutes?: Record<string, any> | Array<any> | null;
  awayProjMinutes?: Record<string, any> | Array<any> | null;
}

export interface BuildPlayerRadarOptions {
  lineups?: PlayerRadarLineupSnapshot | null;
  limit?: number;
  perTeamTarget?: number;
  homeTeamId?: string | null;
  awayTeamId?: string | null;
}

function toNumber(value: any): number | null {
  if (value == null || value === '') return null;
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : null;
}

function round1(value: number): number {
  return Math.round(value * 10) / 10;
}

function normalizeText(value: string | null | undefined): string {
  return String(value || '')
    .trim()
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

function computeVarianceEdgePct(projectedOutcome: number | null, marketLine: number | null): number | null {
  if (projectedOutcome == null || marketLine == null) return null;
  const denominator = Math.max(Math.abs(marketLine), 1);
  return round1((Math.abs(projectedOutcome - marketLine) / denominator) * 100);
}

function classifyVarianceSignal(varianceEdgePct: number | null): PublicFeaturedPlayerProp['varianceSignal'] {
  if (varianceEdgePct == null || varianceEdgePct < 10) return 'COIN_FLIP';
  if (varianceEdgePct >= 25) return 'STRONG';
  if (varianceEdgePct >= 15) return 'GOOD';
  return 'FAIR';
}

function redactLockedFeaturedProp(prop: PublicFeaturedPlayerProp & { __priority: number }): PublicFeaturedPlayerProp & { __priority: number } {
  if (!prop.locked) return prop;
  return {
    ...prop,
    marketLine: null,
    odds: null,
    forecastDirection: null,
    projectedOutcome: null,
    projectedProbability: null,
    marketImpliedProbability: null,
    varianceEdgePct: null,
    varianceSignal: null,
    hasPick: false,
    signalTier: null,
    analysis: null,
  };
}

function normalizeAvailabilityLabel(value: string | null | undefined): 'confirmed' | 'projected' | 'active' | 'unknown' {
  const normalized = String(value || '').trim().toLowerCase();
  if (!normalized) return 'unknown';
  if (normalized.includes('confirmed')) return 'confirmed';
  if (normalized.includes('expected') || normalized.includes('projected')) return 'projected';
  if (normalized.includes('active')) return 'active';
  return 'unknown';
}

function buildLineupSet(players: LineupPlayer[] | null | undefined): Set<string> {
  if (!Array.isArray(players)) return new Set<string>();
  return new Set(
    players
      .map((player) => normalizeText(player?.name || player?.playerName || player?.fullName || null))
      .filter(Boolean),
  );
}

function getLineupPlayerName(player: LineupPlayer | null | undefined): string | null {
  const value = String(player?.name || player?.playerName || player?.fullName || '').trim();
  return value || null;
}

function buildProjectedMinutesMap(
  minutesData: Record<string, any> | Array<any> | null | undefined,
  players: LineupPlayer[] | null | undefined,
): Map<string, number> {
  const map = new Map<string, number>();

  if (Array.isArray(minutesData)) {
    minutesData.forEach((entry, index) => {
      const playerName = normalizeText(
        entry?.name
        || entry?.player
        || entry?.playerName
        || players?.[index]?.name
        || players?.[index]?.playerName
        || players?.[index]?.fullName
        || null,
      );
      const minutes = toNumber(entry?.minutes ?? entry?.projectedMinutes ?? entry?.projMinutes ?? entry);
      if (playerName && minutes != null) map.set(playerName, minutes);
    });
    return map;
  }

  if (minutesData && typeof minutesData === 'object') {
    for (const [key, value] of Object.entries(minutesData)) {
      const playerName = normalizeText(key);
      const minutes = toNumber(
        value && typeof value === 'object'
          ? (value as any).minutes ?? (value as any).projectedMinutes ?? (value as any).projMinutes
          : value,
      );
      if (playerName && minutes != null) map.set(playerName, minutes);
    }
  }

  return map;
}

function buildPropAnalysis(payload: Record<string, any>, locked: boolean): string | null {
  const reasoning = typeof payload.reasoning === 'string' ? payload.reasoning.trim() : '';
  const contextSummary = typeof payload.model_context?.context_summary === 'string'
    ? payload.model_context.context_summary.trim()
    : '';
  if (!locked && reasoning) return reasoning;
  if (contextSummary) return contextSummary;
  if (reasoning) return reasoning;
  return null;
}

function computePropPriority(prop: PublicFeaturedPlayerProp, projectedMinutes: number | null): number {
  const variance = prop.varianceEdgePct ?? 0;
  const pickBonus = prop.hasPick ? 18 : 0;
  const colorBonus = prop.varianceSignal === 'STRONG'
    ? 8
    : prop.varianceSignal === 'GOOD'
      ? 5
      : prop.varianceSignal === 'FAIR'
        ? 2
        : 0;
  const minutesBonus = projectedMinutes != null ? Math.min(projectedMinutes, 40) / 5 : 0;
  return variance + pickBonus + colorBonus + minutesBonus;
}

function computePlayerPriority(
  player: Pick<PublicFeaturedPlayer, 'availability' | 'projectedMinutes' | 'hasPick'> & {
    props: Array<PublicFeaturedPlayerProp & { __priority: number }>;
  },
): number {
  const topPropPriority = player.props[0]?.__priority ?? 0;
  const availabilityBonus = player.availability === 'confirmed'
    ? 14
    : player.availability === 'projected'
      ? 9
      : player.availability === 'active'
        ? 5
        : 0;
  const minutesBonus = player.projectedMinutes != null ? Math.min(player.projectedMinutes, 40) / 4 : 0;
  const pickBonus = player.hasPick ? 4 : 0;
  const depthBonus = Math.min(player.props.length, 3) * 2;
  return topPropPriority + availabilityBonus + minutesBonus + pickBonus + depthBonus;
}

function resolveAvailabilityForPlayer(params: {
  normalizedPlayer: string;
  lineupSet: Set<string>;
  sideAvailability: PublicFeaturedPlayer['availability'];
  explicitAvailability?: PublicFeaturedPlayer['availability'];
}): PublicFeaturedPlayer['availability'] {
  if (params.lineupSet.has(params.normalizedPlayer)) {
    return params.sideAvailability === 'unknown' ? 'active' : params.sideAvailability;
  }
  return params.explicitAvailability && params.explicitAvailability !== 'unknown'
    ? params.explicitAvailability
    : 'unknown';
}

export function buildFeaturedPlayersFromRows(
  rows: RawForecastRow[],
  options: BuildPlayerRadarOptions = {},
): PublicFeaturedPlayer[] {
  if (!Array.isArray(rows) && !options.lineups) return [];
  const sourceRows = Array.isArray(rows) ? rows : [];

  const lineups = options.lineups || null;
  const limit = options.limit ?? 10;
  const perTeamTarget = options.perTeamTarget ?? 5;
  const homeTeamId = options.homeTeamId ?? null;
  const awayTeamId = options.awayTeamId ?? null;
  const homeLineupSet = buildLineupSet(lineups?.homePlayers);
  const awayLineupSet = buildLineupSet(lineups?.awayPlayers);
  const homeMinutesMap = buildProjectedMinutesMap(lineups?.homeProjMinutes, lineups?.homePlayers);
  const awayMinutesMap = buildProjectedMinutesMap(lineups?.awayProjMinutes, lineups?.awayPlayers);
  const homeAvailability = normalizeAvailabilityLabel(lineups?.homeStatus);
  const awayAvailability = normalizeAvailabilityLabel(lineups?.awayStatus);

  const grouped = new Map<string, {
    player: string;
    team: string | null;
    teamSide: 'home' | 'away' | null;
    playerRole: string | null;
    availability: PublicFeaturedPlayer['availability'];
    projectedMinutes: number | null;
    signalStrength: PublicFeaturedPlayer['signalStrength'];
    maxVarianceEdgePct: number | null;
    hasPick: boolean;
    analysis: string | null;
    props: Array<PublicFeaturedPlayerProp & { __priority: number }>;
  }>();

  const ensureSeededPlayer = (params: {
    playerName: string;
    teamSide: 'home' | 'away';
    teamId: string | null;
    lineupSet: Set<string>;
    minutesMap: Map<string, number>;
    sideAvailability: PublicFeaturedPlayer['availability'];
  }) => {
    const normalizedPlayer = normalizeText(params.playerName);
    if (!normalizedPlayer) return;
    const key = `${normalizedPlayer}|${params.teamId || ''}|${params.teamSide}`;
    if (grouped.has(key)) return;
    grouped.set(key, {
      player: params.playerName,
      team: params.teamId,
      teamSide: params.teamSide,
      playerRole: null,
      availability: resolveAvailabilityForPlayer({
        normalizedPlayer,
        lineupSet: params.lineupSet,
        sideAvailability: params.sideAvailability,
      }),
      projectedMinutes: params.minutesMap.get(normalizedPlayer) ?? null,
      signalStrength: null,
      maxVarianceEdgePct: null,
      hasPick: false,
      analysis: null,
      props: [],
    });
  };

  for (const player of lineups?.awayPlayers || []) {
    const playerName = getLineupPlayerName(player);
    if (!playerName) continue;
    ensureSeededPlayer({
      playerName,
      teamSide: 'away',
      teamId: awayTeamId,
      lineupSet: awayLineupSet,
      minutesMap: awayMinutesMap,
      sideAvailability: awayAvailability,
    });
  }

  for (const player of lineups?.homePlayers || []) {
    const playerName = getLineupPlayerName(player);
    if (!playerName) continue;
    ensureSeededPlayer({
      playerName,
      teamSide: 'home',
      teamId: homeTeamId,
      lineupSet: homeLineupSet,
      minutesMap: homeMinutesMap,
      sideAvailability: homeAvailability,
    });
  }

  for (const row of sourceRows) {
    const payload = row.forecast_payload || {};
    const playerName = String(row.player_name || '').trim();
    if (!playerName) continue;

    const marketLine = toNumber(payload.market_line_value ?? payload.line);
    const projectedOutcome = toNumber(payload.projected_stat_value ?? payload.projectedOutcome);
    const projectedProbability = toNumber(payload.projected_probability ?? payload.prob);
    const odds = toNumber(payload.odds);
    const forecastDirection = normalizePlayerPropDirection(payload.forecast_direction ?? payload.recommendation ?? null);
    const signal = buildPlayerPropSignal({
      player: row.player_name,
      team: row.team_id,
      teamSide: row.team_side,
      league: row.league,
      prop: payload.prop ?? null,
      statType: payload.stat_type ?? null,
      normalizedStatType: payload.normalized_stat_type ?? payload.stat_type ?? null,
      marketLine,
      odds,
      projectedProbability,
      projectedOutcome,
      edgePct: toNumber(payload.edge_pct ?? payload.edge),
      recommendation: payload.forecast_direction ?? payload.recommendation ?? null,
      playerRole: payload.player_role ?? row.playerRole ?? null,
      modelContext: payload.model_context ?? null,
      marketQualityScore: toNumber(payload.market_quality_score),
      marketCompletenessStatus: payload.market_completeness_status ?? null,
      sourceBacked: payload.source_backed ?? null,
    });

    const varianceEdgePct = computeVarianceEdgePct(projectedOutcome, marketLine);
    const varianceSignal = classifyVarianceSignal(varianceEdgePct);
    const propType = getPlayerPropTypeLabel({
      league: row.league,
      prop: payload.prop ?? null,
      statType: payload.stat_type ?? null,
      normalizedStatType: payload.normalized_stat_type ?? payload.stat_type ?? null,
    });

    const normalizedPlayer = normalizeText(playerName);
    const lineupSet = row.team_side === 'home' ? homeLineupSet : awayLineupSet;
    const minutesMap = row.team_side === 'home' ? homeMinutesMap : awayMinutesMap;
    const sideAvailability = row.team_side === 'home' ? homeAvailability : awayAvailability;
    const projectedMinutes = minutesMap.get(normalizedPlayer)
      ?? toNumber(payload.model_context?.projected_minutes ?? payload.modelContext?.projectedMinutes)
      ?? null;
    const explicitAvailability = normalizeAvailabilityLabel(payload.model_context?.lineup_certainty);
    const availability = resolveAvailabilityForPlayer({
      normalizedPlayer,
      lineupSet,
      sideAvailability,
      explicitAvailability,
    });

    const prop: PublicFeaturedPlayerProp & { __priority: number } = {
      assetId: row.id,
      propType,
      propLabel: typeof payload.prop === 'string' ? payload.prop : null,
      marketLine,
      odds,
      forecastDirection,
      projectedOutcome,
      projectedProbability,
      marketImpliedProbability: americanOddsToImpliedProbability(odds),
      varianceEdgePct,
      varianceSignal,
      hasPick: Boolean(signal),
      signalTier: signal?.signalTier ?? null,
      locked: Boolean(row.locked),
      analysis: buildPropAnalysis(payload, Boolean(row.locked)),
      __priority: 0,
    };
    prop.__priority = computePropPriority(prop, projectedMinutes);

    const key = `${normalizedPlayer}|${row.team_id || ''}|${row.team_side || ''}`;
    const existing = grouped.get(key);
    if (!existing) {
      grouped.set(key, {
        player: playerName,
        team: row.team_id || null,
        teamSide: row.team_side || null,
        playerRole: String(payload.player_role || row.playerRole || '').trim() || null,
        availability,
        projectedMinutes,
        signalStrength: varianceSignal,
        maxVarianceEdgePct: varianceEdgePct,
        hasPick: Boolean(signal),
        analysis: prop.analysis,
        props: [prop],
      });
      continue;
    }

    existing.props.push(prop);
    if (!existing.player && playerName) {
      existing.player = playerName;
    }
    if (!existing.team && row.team_id) {
      existing.team = row.team_id;
    }
    if ((varianceEdgePct ?? -1) > (existing.maxVarianceEdgePct ?? -1)) {
      existing.maxVarianceEdgePct = varianceEdgePct;
      existing.signalStrength = varianceSignal;
      if (prop.analysis) existing.analysis = prop.analysis;
    }
    if (existing.projectedMinutes == null && projectedMinutes != null) {
      existing.projectedMinutes = projectedMinutes;
    }
    if (!existing.playerRole && (payload.player_role || row.playerRole)) {
      existing.playerRole = String(payload.player_role || row.playerRole);
    }
    existing.hasPick = existing.hasPick || Boolean(signal);
  }

  const ranked = Array.from(grouped.values())
    .map((group) => {
      const props = group.props
        .sort((a, b) => {
          if (b.__priority !== a.__priority) return b.__priority - a.__priority;
          if ((b.varianceEdgePct ?? -1) !== (a.varianceEdgePct ?? -1)) {
            return (b.varianceEdgePct ?? -1) - (a.varianceEdgePct ?? -1);
          }
          if ((b.projectedOutcome ?? -1) !== (a.projectedOutcome ?? -1)) {
            return (b.projectedOutcome ?? -1) - (a.projectedOutcome ?? -1);
          }
          return a.propType.localeCompare(b.propType);
        })
        .map((prop) => redactLockedFeaturedProp(prop));

      const visibleProps = props.filter((prop) => !prop.locked);
      const leadVisibleProp = visibleProps[0] || null;
      const publicMaxVarianceEdgePct = leadVisibleProp?.varianceEdgePct ?? null;
      const publicSignalStrength = leadVisibleProp?.varianceSignal ?? null;
      const publicHasPick = visibleProps.some((prop) => prop.hasPick);
      const publicAnalysis = leadVisibleProp?.analysis ?? null;

      return {
        ...group,
        props,
        maxVarianceEdgePct: publicMaxVarianceEdgePct,
        signalStrength: publicSignalStrength,
        hasPick: publicHasPick,
        analysis: publicAnalysis,
        __priority: computePlayerPriority({ ...group, props }),
      };
    })
    .sort((a, b) => {
      if (b.__priority !== a.__priority) return b.__priority - a.__priority;
      if ((b.maxVarianceEdgePct ?? -1) !== (a.maxVarianceEdgePct ?? -1)) {
        return (b.maxVarianceEdgePct ?? -1) - (a.maxVarianceEdgePct ?? -1);
      }
      return a.player.localeCompare(b.player);
    });

  const selected: typeof ranked = [];
  const seen = new Set<string>();

  for (const teamSide of ['away', 'home'] as const) {
    const teamPlayers = ranked.filter((player) => player.teamSide === teamSide);
    for (const player of teamPlayers.slice(0, perTeamTarget)) {
      const key = `${player.player}|${player.team || ''}|${player.teamSide || ''}`;
      if (seen.has(key)) continue;
      seen.add(key);
      selected.push(player);
    }
  }

  for (const player of ranked) {
    if (selected.length >= limit) break;
    const key = `${player.player}|${player.team || ''}|${player.teamSide || ''}`;
    if (seen.has(key)) continue;
    seen.add(key);
    selected.push(player);
  }

  return selected.slice(0, limit).map((player) => ({
    player: player.player,
    team: player.team,
    teamSide: player.teamSide,
    playerRole: player.playerRole,
    availability: player.availability,
    projectedMinutes: player.projectedMinutes,
    signalStrength: player.signalStrength,
    maxVarianceEdgePct: player.maxVarianceEdgePct,
    hasPick: player.hasPick,
    analysis: player.analysis,
    props: player.props.map(({ __priority, ...prop }) => prop),
  }));
}

export const __playerPropRadarInternals = {
  computeVarianceEdgePct,
  classifyVarianceSignal,
};
