import { and, asc, eq, gte, inArray, lte, or } from "drizzle-orm";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { logger } from "~/lib/logger";

export type GroupStageMatch = typeof schema.groupStageMatches.$inferSelect;

export interface GroupStandingsRow {
  participantId: string;
  participantName: string;
  played: number;
  wins: number;
  draws: number;
  losses: number;
  gf: number; // goals for
  ga: number; // goals against
  gd: number; // goal difference
  points: number;
}

export interface UpcomingGroupMatch {
  matchId: string;
  groupId: string;
  groupName: string;
  sportsSeasonId: string;
  matchday: number;
  scheduledAt: string; // ISO string
  isComplete: boolean;
  participant1: { id: string; name: string };
  participant2: { id: string; name: string };
  participant1Score: number | null;
  participant2Score: number | null;
}

interface MatchInput {
  participant1Id: string;
  participant2Id: string;
  matchday: number;
  scheduledAt?: Date | null;
}

/**
 * Create the 6 round-robin matches for a tournament group.
 * Matches are created without scores (to be filled in as the tournament progresses).
 */
export async function createGroupStageMatches(
  groupId: string,
  matches: MatchInput[]
): Promise<GroupStageMatch[]> {
  const db = database();
  return await db
    .insert(schema.groupStageMatches)
    .values(
      matches.map((m) => ({
        tournamentGroupId: groupId,
        participant1Id: m.participant1Id,
        participant2Id: m.participant2Id,
        matchday: m.matchday,
        scheduledAt: m.scheduledAt ?? null,
      }))
    )
    .returning();
}

/**
 * Generate all 6 round-robin pairings for 4 participants in a group.
 * Uses a fixed round-robin schedule (matchdays 1, 2, 3).
 *
 * Standard 4-team round-robin schedule:
 *   Matchday 1: 1v4, 2v3
 *   Matchday 2: 1v3, 4v2
 *   Matchday 3: 1v2, 3v4
 */
export function generateRoundRobinPairings(
  participantIds: string[]
): MatchInput[] {
  if (participantIds.length !== 4) {
    throw new Error("Round-robin generation requires exactly 4 participants");
  }
  const [p1, p2, p3, p4] = participantIds;
  return [
    { participant1Id: p1, participant2Id: p4, matchday: 1 },
    { participant1Id: p2, participant2Id: p3, matchday: 1 },
    { participant1Id: p1, participant2Id: p3, matchday: 2 },
    { participant1Id: p4, participant2Id: p2, matchday: 2 },
    { participant1Id: p1, participant2Id: p2, matchday: 3 },
    { participant1Id: p3, participant2Id: p4, matchday: 3 },
  ];
}

/**
 * Record the result of a group stage match and mark it complete.
 */
export async function updateGroupStageMatchResult(
  matchId: string,
  participant1Score: number,
  participant2Score: number
): Promise<GroupStageMatch> {
  const db = database();
  const [updated] = await db
    .update(schema.groupStageMatches)
    .set({
      participant1Score,
      participant2Score,
      isComplete: true,
      updatedAt: new Date(),
    })
    .where(eq(schema.groupStageMatches.id, matchId))
    .returning();
  return updated;
}

/**
 * Update the scheduled time of a group stage match.
 */
export async function updateGroupStageMatchSchedule(
  matchId: string,
  scheduledAt: Date | null
): Promise<GroupStageMatch> {
  const db = database();
  const [updated] = await db
    .update(schema.groupStageMatches)
    .set({ scheduledAt, updatedAt: new Date() })
    .where(eq(schema.groupStageMatches.id, matchId))
    .returning();
  return updated;
}

/**
 * Fetch all matches for a group, with participant info.
 */
export async function findMatchesByGroupId(groupId: string) {
  const db = database();
  return await db.query.groupStageMatches.findMany({
    where: eq(schema.groupStageMatches.tournamentGroupId, groupId),
    orderBy: [
      asc(schema.groupStageMatches.matchday),
      asc(schema.groupStageMatches.createdAt),
    ],
    with: {
      participant1: true,
      participant2: true,
    },
  });
}

/**
 * Fetch all matches for multiple groups in a single query.
 * Returns a Map from groupId → matches (ordered by matchday, createdAt).
 * Use this instead of calling findMatchesByGroupId N times.
 */
export async function findMatchesByGroupIds(
  groupIds: string[]
): Promise<Map<string, Awaited<ReturnType<typeof findMatchesByGroupId>>>> {
  if (groupIds.length === 0) return new Map();
  const db = database();
  const rows = await db.query.groupStageMatches.findMany({
    where: inArray(schema.groupStageMatches.tournamentGroupId, groupIds),
    orderBy: [
      asc(schema.groupStageMatches.matchday),
      asc(schema.groupStageMatches.createdAt),
    ],
    with: {
      participant1: true,
      participant2: true,
    },
  });
  const result = new Map<string, typeof rows>();
  for (const groupId of groupIds) result.set(groupId, []);
  for (const row of rows) {
    result.get(row.tournamentGroupId)?.push(row);
  }
  return result;
}

/**
 * Fetch all group stage matches for a scoring event (all groups combined).
 */
export async function findMatchesByEventId(eventId: string) {
  const db = database();
  const groups = await db.query.tournamentGroups.findMany({
    where: eq(schema.tournamentGroups.scoringEventId, eventId),
    with: {
      matches: {
        orderBy: [
          asc(schema.groupStageMatches.matchday),
          asc(schema.groupStageMatches.createdAt),
        ],
        with: {
          participant1: true,
          participant2: true,
        },
      },
    },
  });
  return groups;
}

/**
 * Compute standings for a group from match results.
 * Sorted by: points desc → goal difference desc → goals for desc → name asc.
 */
export function computeGroupStandings(
  members: Array<{ participantId: string; participantName: string }>,
  matches: Array<{
    participant1Id: string;
    participant2Id: string;
    participant1Score: number | null;
    participant2Score: number | null;
    isComplete: boolean;
  }>
): GroupStandingsRow[] {
  const stats = new Map<string, GroupStandingsRow>();

  for (const m of members) {
    stats.set(m.participantId, {
      participantId: m.participantId,
      participantName: m.participantName,
      played: 0,
      wins: 0,
      draws: 0,
      losses: 0,
      gf: 0,
      ga: 0,
      gd: 0,
      points: 0,
    });
  }

  for (const match of matches) {
    if (!match.isComplete) continue;
    if (match.participant1Score === null || match.participant2Score === null) {
      logger.warn(
        `[computeGroupStandings] Match between ${match.participant1Id} and ${match.participant2Id} is marked complete but has null scores — skipping`
      );
      continue;
    }

    const p1 = stats.get(match.participant1Id);
    const p2 = stats.get(match.participant2Id);
    if (!p1 || !p2) continue;

    const s1 = match.participant1Score;
    const s2 = match.participant2Score;

    p1.played++;
    p2.played++;
    p1.gf += s1;
    p1.ga += s2;
    p2.gf += s2;
    p2.ga += s1;
    p1.gd = p1.gf - p1.ga;
    p2.gd = p2.gf - p2.ga;

    if (s1 > s2) {
      p1.wins++;
      p1.points += 3;
      p2.losses++;
    } else if (s2 > s1) {
      p2.wins++;
      p2.points += 3;
      p1.losses++;
    } else {
      p1.draws++;
      p2.draws++;
      p1.points++;
      p2.points++;
    }
  }

  return [...stats.values()].toSorted((a, b) => {
    if (b.points !== a.points) return b.points - a.points;
    if (b.gd !== a.gd) return b.gd - a.gd;
    if (b.gf !== a.gf) return b.gf - a.gf;
    return a.participantName.localeCompare(b.participantName);
  });
}

/**
 * Get upcoming group stage matches (within a date window) for specific participants.
 * Used for calendar/schedule display on league home and team pages.
 */
export async function getUpcomingGroupStageMatchesForParticipants(
  sportsSeasonId: string,
  participantIds: string[],
  dateFrom: Date,
  dateTo: Date
): Promise<UpcomingGroupMatch[]> {
  if (participantIds.length === 0) return [];

  const db = database();

  const rows = await db
    .select({
      matchId: schema.groupStageMatches.id,
      groupId: schema.tournamentGroups.id,
      groupName: schema.tournamentGroups.groupName,
      sportsSeasonId: schema.scoringEvents.sportsSeasonId,
      matchday: schema.groupStageMatches.matchday,
      scheduledAt: schema.groupStageMatches.scheduledAt,
      isComplete: schema.groupStageMatches.isComplete,
      participant1Id: schema.groupStageMatches.participant1Id,
      participant2Id: schema.groupStageMatches.participant2Id,
      participant1Score: schema.groupStageMatches.participant1Score,
      participant2Score: schema.groupStageMatches.participant2Score,
    })
    .from(schema.groupStageMatches)
    .innerJoin(
      schema.tournamentGroups,
      eq(schema.groupStageMatches.tournamentGroupId, schema.tournamentGroups.id)
    )
    .innerJoin(
      schema.scoringEvents,
      eq(schema.tournamentGroups.scoringEventId, schema.scoringEvents.id)
    )
    .where(
      and(
        eq(schema.scoringEvents.sportsSeasonId, sportsSeasonId),
        or(
          inArray(schema.groupStageMatches.participant1Id, participantIds),
          inArray(schema.groupStageMatches.participant2Id, participantIds)
        ),
        and(
          gte(schema.groupStageMatches.scheduledAt, dateFrom),
          lte(schema.groupStageMatches.scheduledAt, dateTo)
        )
      )
    )
    .orderBy(asc(schema.groupStageMatches.scheduledAt));

  if (rows.length === 0) return [];

  // Load participant names for all involved participants
  const allParticipantIds = [
    ...new Set(rows.flatMap((r) => [r.participant1Id, r.participant2Id])),
  ];
  const participantRows = await db.query.seasonParticipants.findMany({
    where: inArray(schema.seasonParticipants.id, allParticipantIds),
  });
  const participantMap = new Map(participantRows.map((p) => [p.id, p]));

  return rows
    .filter((r) => r.scheduledAt !== null)
    .map((r) => {
      const p1 = participantMap.get(r.participant1Id);
      const p2 = participantMap.get(r.participant2Id);
      return {
        matchId: r.matchId,
        groupId: r.groupId,
        groupName: r.groupName,
        sportsSeasonId: r.sportsSeasonId,
        matchday: r.matchday,
        scheduledAt: r.scheduledAt?.toISOString() ?? "",
        isComplete: r.isComplete,
        participant1: { id: r.participant1Id, name: p1?.name ?? "Unknown" },
        participant2: { id: r.participant2Id, name: p2?.name ?? "Unknown" },
        participant1Score: r.participant1Score,
        participant2Score: r.participant2Score,
      };
    });
}
