import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq, and } from "drizzle-orm";
import type { TeamStanding, TeamStandingSnapshot, TeamStandingWithChange } from "~/types/standings";
import { calculateBracketPoints, calculateFantasyPoints } from "~/models/scoring-rules";
import { logger } from "~/lib/logger";

// Re-export types from shared types file
export type { TeamStanding, TeamStandingSnapshot, TeamStandingWithChange } from "~/types/standings";

/**
 * Get current standings for a season
 */
export async function getSeasonStandings(
  seasonId: string,
  providedDb?: ReturnType<typeof database>
): Promise<TeamStanding[]> {
  const db = providedDb || database();

  const standings = await db.query.teamStandings.findMany({
    where: eq(schema.teamStandings.seasonId, seasonId),
    with: {
      team: true,
    },
  });

  // Sort by currentRank ascending (1 is best, 2 is second, etc.)
  const sorted = standings.toSorted((a, b) => a.currentRank - b.currentRank);

  return sorted.map((standing) => ({
    teamId: standing.teamId,
    teamName: standing.team.name,
    totalPoints: parseFloat(standing.totalPoints),
    currentRank: standing.currentRank,
    previousRank: standing.previousRank,
    rankChange: standing.previousRank
      ? standing.previousRank - standing.currentRank
      : 0,
    placementCounts: {
      first: standing.firstPlaceCount,
      second: standing.secondPlaceCount,
      third: standing.thirdPlaceCount,
      fourth: standing.fourthPlaceCount,
      fifth: standing.fifthPlaceCount,
      sixth: standing.sixthPlaceCount,
      seventh: standing.seventhPlaceCount,
      eighth: standing.eighthPlaceCount,
    },
    participantsRemaining: standing.participantsRemaining,
    calculatedAt: standing.calculatedAt,
    // Phase 5.4: Include projected points
    actualPoints: standing.actualPoints ? parseFloat(standing.actualPoints) : null,
    projectedPoints: standing.projectedPoints ? parseFloat(standing.projectedPoints) : null,
    participantsFinished: standing.participantsFinished,
  }));
}

/**
 * Get standings for a specific team
 */
export async function getTeamStanding(
  teamId: string,
  seasonId: string,
  providedDb?: ReturnType<typeof database>
): Promise<TeamStanding | null> {
  const db = providedDb || database();

  const standing = await db.query.teamStandings.findFirst({
    where: and(
      eq(schema.teamStandings.teamId, teamId),
      eq(schema.teamStandings.seasonId, seasonId)
    ),
    with: {
      team: true,
    },
  });

  if (!standing) return null;

  return {
    teamId: standing.teamId,
    teamName: standing.team.name,
    totalPoints: parseFloat(standing.totalPoints),
    currentRank: standing.currentRank,
    previousRank: standing.previousRank,
    rankChange: standing.previousRank
      ? standing.previousRank - standing.currentRank
      : 0,
    placementCounts: {
      first: standing.firstPlaceCount,
      second: standing.secondPlaceCount,
      third: standing.thirdPlaceCount,
      fourth: standing.fourthPlaceCount,
      fifth: standing.fifthPlaceCount,
      sixth: standing.sixthPlaceCount,
      seventh: standing.seventhPlaceCount,
      eighth: standing.eighthPlaceCount,
    },
    participantsRemaining: standing.participantsRemaining,
    calculatedAt: standing.calculatedAt,
    // Phase 5.4: Include projected points
    actualPoints: standing.actualPoints ? parseFloat(standing.actualPoints) : null,
    projectedPoints: standing.projectedPoints ? parseFloat(standing.projectedPoints) : null,
    participantsFinished: standing.participantsFinished,
  };
}

/**
 * Get detailed team breakdown with all picks and their points
 */
export async function getTeamScoreBreakdown(
  teamId: string,
  seasonId: string,
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  // Get season scoring rules
  const season = await db.query.seasons.findFirst({
    where: eq(schema.seasons.id, seasonId),
  });

  if (!season) return null;

  // Get all draft picks for this team with participant details
  const picks = await db.query.draftPicks.findMany({
    where: and(
      eq(schema.draftPicks.teamId, teamId),
      eq(schema.draftPicks.seasonId, seasonId)
    ),
    orderBy: schema.draftPicks.pickNumber,
    with: {
      participant: {
        with: {
          sportsSeason: {
            with: {
              sport: true,
            },
          },
          results: true,
        },
      },
    },
  });

  // Get scoring rules for EV calculation
  const scoringRules = {
    pointsFor1st: season.pointsFor1st,
    pointsFor2nd: season.pointsFor2nd,
    pointsFor3rd: season.pointsFor3rd,
    pointsFor4th: season.pointsFor4th,
    pointsFor5th: season.pointsFor5th,
    pointsFor6th: season.pointsFor6th,
    pointsFor7th: season.pointsFor7th,
    pointsFor8th: season.pointsFor8th,
  };

  // Cache bracket template IDs per sports season (same approach as calculateTeamScore)
  const bracketTemplateCache = new Map<string, string | null>();
  async function getBracketTemplate(sportsSeasonId: string): Promise<string | null> {
    if (bracketTemplateCache.has(sportsSeasonId)) {
      return bracketTemplateCache.get(sportsSeasonId) ?? null;
    }
    const event = await db.query.scoringEvents.findFirst({
      where: eq(schema.scoringEvents.sportsSeasonId, sportsSeasonId),
      columns: { bracketTemplateId: true },
    });
    const templateId = event?.bracketTemplateId ?? null;
    bracketTemplateCache.set(sportsSeasonId, templateId);
    return templateId;
  }

  // Calculate points and projected points for each pick
  const pickBreakdown = await Promise.all(
    picks.map(async (pick) => {
      const result = pick.participant.results[0];
      const isBracket = pick.participant.sportsSeason.scoringPattern === "playoff_bracket";
      let points = 0;
      let projectedPoints: number | null = null;

      const getEV = async () => {
        const { getParticipantEV } = await import("./participant-expected-value");
        const { calculateEV } = await import("~/services/ev-calculator");
        const ev = await getParticipantEV(pick.participant.id, pick.participant.sportsSeasonId);
        if (!ev) return null;
        return calculateEV(
          {
            probFirst: parseFloat(ev.probFirst),
            probSecond: parseFloat(ev.probSecond),
            probThird: parseFloat(ev.probThird),
            probFourth: parseFloat(ev.probFourth),
            probFifth: parseFloat(ev.probFifth),
            probSixth: parseFloat(ev.probSixth),
            probSeventh: parseFloat(ev.probSeventh),
            probEighth: parseFloat(ev.probEighth),
          },
          scoringRules
        );
      };

      if (result && result.finalPosition !== null && result.finalPosition > 0) {
        // Calculate points using bracket-averaged scoring for bracket sports
        if (isBracket) {
          const templateId = await getBracketTemplate(pick.participant.sportsSeasonId);
          points = calculateBracketPoints(result.finalPosition, scoringRules, templateId);
        } else {
          points = calculateFantasyPoints(result.finalPosition, scoringRules);
        }

        if (result.isPartialScore) {
          // Still alive with a floor position — use EV for projected since they can advance
          projectedPoints = (await getEV()) ?? points;
        } else {
          projectedPoints = points; // Finalized: projected equals actual
        }
      } else {
        // Participant is unfinished - get EV
        projectedPoints = await getEV();
      }

      return {
        pickNumber: pick.pickNumber,
        round: pick.round,
        participant: {
          id: pick.participant.id,
          name: pick.participant.name,
          sport: pick.participant.sportsSeason.sport.name,
          sportsSeasonId: pick.participant.sportsSeasonId,
        },
        finalPosition: result?.finalPosition ?? null,
        points,
        projectedPoints,
        // isComplete: has a result record (even if partial/floor)
        isComplete: !!result,
        // isPartialScore: still alive with a provisional floor position
        isPartialScore: result?.isPartialScore ?? false,
      };
    })
  );

  const actualPoints = pickBreakdown
    .filter((p) => p.isComplete)
    .reduce((sum, p) => sum + p.points, 0);

  const projectedTotalPoints = pickBreakdown.reduce(
    (sum, p) => sum + (p.projectedPoints ?? 0),
    0
  );

  return {
    team: await db.query.teams.findFirst({
      where: eq(schema.teams.id, teamId),
    }),
    picks: pickBreakdown,
    actualPoints,
    projectedPoints: projectedTotalPoints,
    completedCount: pickBreakdown.filter((p) => p.isComplete && !p.isPartialScore).length,
    totalCount: pickBreakdown.length,
  };
}

/**
 * Get historical standings snapshots for a team
 * Used to display point progression over time
 */
export async function getTeamStandingsHistory(
  teamId: string,
  seasonId: string,
  providedDb?: ReturnType<typeof database>
): Promise<TeamStandingSnapshot[]> {
  const db = providedDb || database();

  const snapshots = await db.query.teamStandingsSnapshots.findMany({
    where: and(
      eq(schema.teamStandingsSnapshots.teamId, teamId),
      eq(schema.teamStandingsSnapshots.seasonId, seasonId)
    ),
    orderBy: schema.teamStandingsSnapshots.snapshotDate,
  });

  return snapshots.map((snapshot) => ({
    date: new Date(snapshot.snapshotDate),
    rank: snapshot.rank,
    totalPoints: parseFloat(snapshot.totalPoints),
  }));
}

/**
 * Get standings comparison between current and 7 days ago
 * Used for "7-day change" display
 */
export async function getSevenDayStandingsChange(
  seasonId: string,
  providedDb?: ReturnType<typeof database>
): Promise<TeamStandingWithChange[]> {
  const db = providedDb || database();

  const sevenDaysAgo = new Date();
  sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);

  // Get current standings
  const current = await getSeasonStandings(seasonId, db);

  // Get snapshots from 7 days ago
  const snapshots = await db.query.teamStandingsSnapshots.findMany({
    where: and(
      eq(schema.teamStandingsSnapshots.seasonId, seasonId),
      eq(schema.teamStandingsSnapshots.snapshotDate, `${sevenDaysAgo.getFullYear()}-${String(sevenDaysAgo.getMonth() + 1).padStart(2, "0")}-${String(sevenDaysAgo.getDate()).padStart(2, "0")}`)
    ),
  });

  // Create a map of team -> old rank and old points
  const oldRanks = new Map<string, number>();
  const oldPoints = new Map<string, number>();
  for (const snapshot of snapshots) {
    oldRanks.set(snapshot.teamId, snapshot.rank);
    const snapshotPoints = snapshot.actualPoints
      ? parseFloat(snapshot.actualPoints)
      : parseFloat(snapshot.totalPoints);
    oldPoints.set(snapshot.teamId, snapshotPoints);
  }

  // Add 7-day changes to current standings
  return current.map((standing) => {
    const currentPoints = standing.actualPoints ?? standing.totalPoints;
    const oldPoint = oldPoints.get(standing.teamId);
    return {
      ...standing,
      sevenDayRankChange: oldRanks.has(standing.teamId)
        ? (oldRanks.get(standing.teamId) ?? 0) - standing.currentRank
        : 0,
      sevenDayOldRank: oldRanks.get(standing.teamId) || null,
      sevenDayPointChange: oldPoint !== undefined ? currentPoints - oldPoint : 0,
    };
  });
}

/**
 * Create a daily standings snapshot
 * Should be called by a scheduled job once per day
 */
export async function createDailySnapshot(
  seasonId: string,
  providedDb?: ReturnType<typeof database>
): Promise<void> {
  const db = providedDb || database();

  const now = new Date();
  const today = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;

  const standings = await db.query.teamStandings.findMany({
    where: eq(schema.teamStandings.seasonId, seasonId),
  });

  await db.transaction(async (tx) => {
    for (const standing of standings) {
      const snapshotData = {
        totalPoints: standing.totalPoints,
        rank: standing.currentRank,
        firstPlaceCount: standing.firstPlaceCount,
        secondPlaceCount: standing.secondPlaceCount,
        thirdPlaceCount: standing.thirdPlaceCount,
        fourthPlaceCount: standing.fourthPlaceCount,
        fifthPlaceCount: standing.fifthPlaceCount,
        sixthPlaceCount: standing.sixthPlaceCount,
        seventhPlaceCount: standing.seventhPlaceCount,
        eighthPlaceCount: standing.eighthPlaceCount,
        participantsRemaining: standing.participantsRemaining,
        actualPoints: standing.actualPoints,
        projectedPoints: standing.projectedPoints,
        participantsFinished: standing.participantsFinished,
      };

      await tx
        .insert(schema.teamStandingsSnapshots)
        .values({ teamId: standing.teamId, seasonId, snapshotDate: today, ...snapshotData })
        .onConflictDoUpdate({
          target: [
            schema.teamStandingsSnapshots.teamId,
            schema.teamStandingsSnapshots.seasonId,
            schema.teamStandingsSnapshots.snapshotDate,
          ],
          set: snapshotData,
        });
    }
  });

  logger.log(`[Standings] Upserted daily snapshot for season ${seasonId}`);
}

/**
 * Get point progression data for all teams in a season
 * Returns historical snapshots organized by team for charting
 */
export async function getSeasonPointProgression(
  seasonId: string,
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  // Get all snapshots for this season
  const snapshots = await db.query.teamStandingsSnapshots.findMany({
    where: eq(schema.teamStandingsSnapshots.seasonId, seasonId),
    orderBy: schema.teamStandingsSnapshots.snapshotDate,
    with: {
      team: true,
    },
  });

  // Get unique teams
  const teams = await db.query.teams.findMany({
    where: eq(schema.teams.seasonId, seasonId),
  });

  // Organize data by date
  const dateMap = new Map<string, { date: string; [teamName: string]: string | number }>();

  for (const snapshot of snapshots) {
    const date = snapshot.snapshotDate;

    if (!dateMap.has(date)) {
      dateMap.set(date, { date });
    }

    const dateData = dateMap.get(date);
    if (!dateData) continue;
    dateData[snapshot.team.name] = parseFloat(snapshot.totalPoints);
  }

  // Convert to array and sort by date
  const chartData = Array.from(dateMap.values()).toSorted((a, b) =>
    new Date(a.date).getTime() - new Date(b.date).getTime()
  );

  return {
    chartData,
    teams: teams.map(t => ({ id: t.id, name: t.name })),
  };
}
