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

export type PlayoffMatchOdds = typeof schema.playoffMatchOdds.$inferSelect;
export type NewPlayoffMatchOdds = typeof schema.playoffMatchOdds.$inferInsert;

// ─── Pure helpers ────────────────────────────────────────────────────────────

/**
 * Convert American moneyline odds to raw implied probability (includes vig).
 *
 * Positive odds (e.g. +150): probability = 100 / (odds + 100)
 * Negative odds (e.g. -110): probability = |odds| / (|odds| + 100)
 *
 * Returns a value in [0, 1].
 */
export function americanToImpliedProbability(moneyline: number): number {
  if (moneyline === 0) return 0;
  if (moneyline > 0) {
    return 100 / (moneyline + 100);
  }
  const abs = Math.abs(moneyline);
  return abs / (abs + 100);
}

/**
 * Convert an implied probability (0–1) to American moneyline odds.
 *
 * prob >= 0.5 → negative (favourite): -(prob / (1 - prob)) * 100
 * prob <  0.5 → positive (underdog):  ((1 - prob) / prob) * 100
 *
 * Returns rounded integer.
 */
export function impliedProbabilityToAmerican(probability: number): number {
  if (probability <= 0 || probability >= 1) {
    throw new RangeError("Probability must be strictly between 0 and 1");
  }
  if (probability >= 0.5) {
    return Math.round(-(probability / (1 - probability)) * 100);
  }
  return Math.round(((1 - probability) / probability) * 100);
}

/**
 * Remove the vig from a two-way market to get fair probabilities summing to 1.
 *
 * Returns [fairP1, fairP2].
 */
export function normalizeOdds(
  moneyline1: number,
  moneyline2: number
): [number, number] {
  const raw1 = americanToImpliedProbability(moneyline1);
  const raw2 = americanToImpliedProbability(moneyline2);
  const total = raw1 + raw2;
  return [raw1 / total, raw2 / total];
}

// ─── DB operations ───────────────────────────────────────────────────────────

export async function findOddsByMatchId(
  playoffMatchId: string
): Promise<PlayoffMatchOdds[]> {
  const db = database();
  return await db.query.playoffMatchOdds.findMany({
    where: eq(schema.playoffMatchOdds.playoffMatchId, playoffMatchId),
    with: { participant: true },
  });
}

export async function findOddsForParticipant(
  playoffMatchId: string,
  participantId: string
): Promise<PlayoffMatchOdds | undefined> {
  const db = database();
  return await db.query.playoffMatchOdds.findFirst({
    where: and(
      eq(schema.playoffMatchOdds.playoffMatchId, playoffMatchId),
      eq(schema.playoffMatchOdds.participantId, participantId)
    ),
  });
}

/**
 * Insert or update odds for a participant in a matchup.
 * One row per (playoffMatchId, participantId) — overwritten on each call.
 * Implied probability is computed automatically from moneylineOdds.
 */
export async function upsertMatchOdds(
  playoffMatchId: string,
  participantId: string,
  data: {
    moneylineOdds: number;
    oddsSource?: string;
  }
): Promise<PlayoffMatchOdds> {
  const db = database();

  const impliedProbability = americanToImpliedProbability(data.moneylineOdds).toFixed(4);
  const payload = { ...data, impliedProbability };

  const existing = await findOddsForParticipant(playoffMatchId, participantId);

  if (existing) {
    const [updated] = await db
      .update(schema.playoffMatchOdds)
      .set({ ...payload, recordedAt: new Date(), updatedAt: new Date() })
      .where(eq(schema.playoffMatchOdds.id, existing.id))
      .returning();
    return updated;
  }

  const [created] = await db
    .insert(schema.playoffMatchOdds)
    .values({ playoffMatchId, participantId, ...payload })
    .returning();
  return created;
}

export async function deleteOddsByMatchId(playoffMatchId: string): Promise<void> {
  const db = database();
  await db
    .delete(schema.playoffMatchOdds)
    .where(eq(schema.playoffMatchOdds.playoffMatchId, playoffMatchId));
}

export async function deleteOddsForParticipant(
  playoffMatchId: string,
  participantId: string
): Promise<void> {
  const db = database();
  await db
    .delete(schema.playoffMatchOdds)
    .where(
      and(
        eq(schema.playoffMatchOdds.playoffMatchId, playoffMatchId),
        eq(schema.playoffMatchOdds.participantId, participantId)
      )
    );
}
