import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq, and, desc, sql } from "drizzle-orm";

/**
 * Default qualifying point values (as specified in scoring-system.md)
 */
export const DEFAULT_QP_VALUES = [
  { placement: 1, points: 20 },
  { placement: 2, points: 14 },
  { placement: 3, points: 10 },
  { placement: 4, points: 8 },
  { placement: 5, points: 5 },
  { placement: 6, points: 5 },
  { placement: 7, points: 3 },
  { placement: 8, points: 3 },
  { placement: 9, points: 2 },
  { placement: 10, points: 2 },
  { placement: 11, points: 2 },
  { placement: 12, points: 2 },
  { placement: 13, points: 1 },
  { placement: 14, points: 1 },
  { placement: 15, points: 1 },
  { placement: 16, points: 1 },
];

export interface QualifyingPointConfigData {
  placement: number;
  points: number;
}

/**
 * Initialize default qualifying point configuration for a sports season
 */
export async function initializeQPConfig(
  sportsSeasonId: string,
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  // Check if config already exists
  const existing = await db.query.qualifyingPointConfig.findMany({
    where: eq(schema.qualifyingPointConfig.sportsSeasonId, sportsSeasonId),
  });

  if (existing.length > 0) {
    return existing;
  }

  // Insert default config
  const configs = await db
    .insert(schema.qualifyingPointConfig)
    .values(
      DEFAULT_QP_VALUES.map((qp) => ({
        sportsSeasonId,
        placement: qp.placement,
        points: qp.points.toString(),
      }))
    )
    .returning();

  return configs;
}

/**
 * Get qualifying point configuration for a sports season
 */
export async function getQPConfig(
  sportsSeasonId: string,
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  const configs = await db.query.qualifyingPointConfig.findMany({
    where: eq(schema.qualifyingPointConfig.sportsSeasonId, sportsSeasonId),
    orderBy: [schema.qualifyingPointConfig.placement],
  });

  // If no config exists, initialize with defaults
  if (configs.length === 0) {
    return await initializeQPConfig(sportsSeasonId, db);
  }

  return configs;
}

/**
 * Update qualifying point configuration for a sports season
 */
export async function updateQPConfig(
  sportsSeasonId: string,
  configData: QualifyingPointConfigData[],
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  // Delete existing config
  await db
    .delete(schema.qualifyingPointConfig)
    .where(eq(schema.qualifyingPointConfig.sportsSeasonId, sportsSeasonId));

  // Insert new config
  const configs = await db
    .insert(schema.qualifyingPointConfig)
    .values(
      configData.map((qp) => ({
        sportsSeasonId,
        placement: qp.placement,
        points: qp.points.toString(),
      }))
    )
    .returning();

  return configs;
}

/**
 * Get qualifying points awarded for a specific placement
 */
export async function getQPForPlacement(
  sportsSeasonId: string,
  placement: number,
  providedDb?: ReturnType<typeof database>
): Promise<number> {
  const db = providedDb || database();

  const config = await db.query.qualifyingPointConfig.findFirst({
    where: and(
      eq(schema.qualifyingPointConfig.sportsSeasonId, sportsSeasonId),
      eq(schema.qualifyingPointConfig.placement, placement)
    ),
  });

  return config ? parseFloat(config.points) : 0;
}

/**
 * Get or create participant qualifying total record
 */
export async function getOrCreateParticipantQPTotal(
  participantId: string,
  sportsSeasonId: string,
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  let total = await db.query.participantQualifyingTotals.findFirst({
    where: and(
      eq(schema.participantQualifyingTotals.participantId, participantId),
      eq(schema.participantQualifyingTotals.sportsSeasonId, sportsSeasonId)
    ),
  });

  if (!total) {
    const [created] = await db
      .insert(schema.participantQualifyingTotals)
      .values({
        participantId,
        sportsSeasonId,
        totalQualifyingPoints: "0",
        eventsScored: 0,
      })
      .returning();
    total = created;
  }

  return total;
}

/**
 * Add qualifying points to a participant's total
 * NOTE: eventsScored is NOT updated here - it should be recalculated after processing events
 */
export async function addQualifyingPoints(
  participantId: string,
  sportsSeasonId: string,
  pointsToAdd: number,
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  // Get or create the total record
  const total = await getOrCreateParticipantQPTotal(participantId, sportsSeasonId, db);

  // Update with new points (do NOT increment eventsScored here)
  const [updated] = await db
    .update(schema.participantQualifyingTotals)
    .set({
      totalQualifyingPoints: (parseFloat(total.totalQualifyingPoints) + pointsToAdd).toString(),
      updatedAt: new Date(),
    })
    .where(eq(schema.participantQualifyingTotals.id, total.id))
    .returning();

  return updated;
}

/**
 * Recalculate total QP and eventsScored for a participant from scratch
 * This is the source of truth - sums all event_results with QP awarded
 */
export async function recalculateParticipantQP(
  participantId: string,
  sportsSeasonId: string,
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  // Get all event results for this participant in this sports season
  const eventResults = await db.query.eventResults.findMany({
    where: eq(schema.eventResults.participantId, participantId),
    with: {
      scoringEvent: true,
    },
  });

  // Calculate totals from event_results
  let totalQP = 0;
  const uniqueEvents = new Set<string>();

  for (const result of eventResults) {
    if (
      result.scoringEvent.sportsSeasonId === sportsSeasonId &&
      result.qualifyingPointsAwarded
    ) {
      const qp = parseFloat(result.qualifyingPointsAwarded);
      if (qp > 0) {
        totalQP += qp;
        uniqueEvents.add(result.scoringEvent.id);
      }
    }
  }

  const eventsScored = uniqueEvents.size;

  // Get or create the participant's QP total record
  const total = await getOrCreateParticipantQPTotal(participantId, sportsSeasonId, db);

  // Update with recalculated values
  await db
    .update(schema.participantQualifyingTotals)
    .set({
      totalQualifyingPoints: totalQP.toString(),
      eventsScored,
      updatedAt: new Date(),
    })
    .where(eq(schema.participantQualifyingTotals.id, total.id));

  return { totalQP, eventsScored };
}

/**
 * Recalculate eventsScored for a participant by counting unique events with QP awarded
 * @deprecated Use recalculateParticipantQP instead for more accurate recalculation
 */
export async function recalculateEventsScored(
  participantId: string,
  sportsSeasonId: string,
  providedDb?: ReturnType<typeof database>
) {
  const result = await recalculateParticipantQP(participantId, sportsSeasonId, providedDb);
  return result.eventsScored;
}

/**
 * Get all qualifying totals for a sports season, ordered by points (highest first)
 */
export async function getQPStandings(
  sportsSeasonId: string,
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  return await db.query.participantQualifyingTotals.findMany({
    where: eq(schema.participantQualifyingTotals.sportsSeasonId, sportsSeasonId),
    orderBy: [desc(sql`CAST(${schema.participantQualifyingTotals.totalQualifyingPoints} AS DECIMAL)`)],
    with: {
      participant: true,
    },
  });
}

/**
 * Update final rankings for qualifying point standings (after finalization)
 */
export async function updateFinalRankings(
  sportsSeasonId: string,
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  const standings = await getQPStandings(sportsSeasonId, db);

  // Group participants by QP total to handle ties
  const groupedByPoints = new Map<string, typeof standings>();
  for (const standing of standings) {
    const points = standing.totalQualifyingPoints;
    if (!groupedByPoints.has(points)) {
      groupedByPoints.set(points, []);
    }
    groupedByPoints.get(points)?.push(standing);
  }

  // Assign rankings, handling ties
  let currentRank = 1;
  const updates = [];

  // Sort by points descending
  const sortedGroups = Array.from(groupedByPoints.entries()).toSorted((a, b) => {
    return parseFloat(b[0]) - parseFloat(a[0]);
  });

  for (const [_, group] of sortedGroups) {
    // All participants in this group get the same rank
    for (const standing of group) {
      updates.push({
        id: standing.id,
        finalRanking: currentRank,
      });
    }
    // Increment rank by the size of the group
    currentRank += group.length;
  }

  // Update all rankings
  for (const update of updates) {
    await db
      .update(schema.participantQualifyingTotals)
      .set({ finalRanking: update.finalRanking, updatedAt: new Date() })
      .where(eq(schema.participantQualifyingTotals.id, update.id));
  }

  return updates;
}

/**
 * Reset qualifying points for a sports season (useful for testing or corrections)
 */
export async function resetQualifyingPoints(
  sportsSeasonId: string,
  providedDb?: ReturnType<typeof database>
) {
  const db = providedDb || database();

  await db
    .delete(schema.participantQualifyingTotals)
    .where(eq(schema.participantQualifyingTotals.sportsSeasonId, sportsSeasonId));
}
