import type { Db } from '@pintrush/db';
import { parseEventConfig, parseVenueSettings } from '@pintrush/db';
import type { CategoryDef, QuestionInput } from '@pintrush/engine';
import { log } from '../logger';
import type { Catalog, EventRecord, PackRecord, RoomRecord, VenueRecord } from './types';

const PACK_TTL_MS = 5 * 60_000;

type VenueRow = { id: string; slug: string; name: string; joinCode: string; timezone: string; settings: unknown };
type EventRow = {
  id: string;
  venueId: string;
  roomId: string;
  kind: 'ALWAYS_ON' | 'CHAMPIONSHIP';
  status: EventRecord['status'];
  name: string;
  scheduledAt: Date | null;
  businessDate: string | null;
  config: unknown;
};

export function toVenueRecord(v: VenueRow): VenueRecord {
  let settings: VenueRecord['settings'];
  try {
    settings = parseVenueSettings(v.settings);
  } catch (err) {
    // A bad settings blob must not take a whole room down: fall back to defaults (always-on off).
    log.warn('invalid venue settings; using defaults', { venueId: v.id, err });
    settings = parseVenueSettings({});
  }
  return { id: v.id, slug: v.slug, name: v.name, joinCode: v.joinCode, timezone: v.timezone, settings };
}

export function toEventRecord(e: EventRow): EventRecord {
  const raw = (e.config ?? {}) as { mergeSolos?: unknown };
  let config: EventRecord['config'];
  if (e.kind === 'ALWAYS_ON') {
    // Always-on nights have no plan (endless Rush rounds); only the solo preference matters.
    config = { plan: [], mergeSolos: raw.mergeSolos === true };
  } else {
    try {
      config = parseEventConfig(e.config);
    } catch (err) {
      log.warn('invalid event config; using the default championship plan', { eventId: e.id, err });
      config = parseEventConfig({});
    }
  }
  return {
    id: e.id,
    venueId: e.venueId,
    roomId: e.roomId,
    kind: e.kind,
    status: e.status,
    name: e.name,
    scheduledAt: e.scheduledAt ? e.scheduledAt.getTime() : null,
    businessDate: e.businessDate,
    config,
  };
}

export class PrismaCatalog implements Catalog {
  private readonly packs = new Map<string, { at: number; pack: PackRecord }>();

  constructor(
    private readonly db: Db,
    private readonly now: () => number,
  ) {}

  async findVenueByJoinCode(joinCode: string): Promise<VenueRecord | null> {
    const v = await this.db.venue.findFirst({ where: { joinCode: { equals: joinCode, mode: 'insensitive' } } });
    return v && v.status !== 'CANCELLED' ? toVenueRecord(v) : null;
  }

  async findVenue(venueId: string): Promise<VenueRecord | null> {
    const v = await this.db.venue.findUnique({ where: { id: venueId } });
    return v ? toVenueRecord(v) : null;
  }

  async listRooms(venueId: string): Promise<RoomRecord[]> {
    const rows = await this.db.room.findMany({ where: { venueId, status: 'ACTIVE' }, orderBy: { createdAt: 'asc' } });
    return rows.map((r) => ({ id: r.id, venueId: r.venueId, name: r.name, slug: r.slug, tvTokenVersion: r.tvTokenVersion, status: r.status }));
  }

  async findRoom(roomId: string): Promise<RoomRecord | null> {
    const r = await this.db.room.findUnique({ where: { id: roomId } });
    return r ? { id: r.id, venueId: r.venueId, name: r.name, slug: r.slug, tvTokenVersion: r.tvTokenVersion, status: r.status } : null;
  }

  async findEvent(eventId: string): Promise<EventRecord | null> {
    const e = await this.db.event.findUnique({ where: { id: eventId } });
    return e ? toEventRecord(e) : null;
  }

  async listAlwaysOnVenues(): Promise<VenueRecord[]> {
    const rows = await this.db.venue.findMany({ where: { status: { in: ['PILOT', 'ACTIVE'] } } });
    return rows.map(toVenueRecord).filter((v) => v.settings.alwaysOn.enabled);
  }

  async championshipsToOpen(untilMs: number): Promise<EventRecord[]> {
    const rows = await this.db.event.findMany({
      where: { kind: 'CHAMPIONSHIP', status: 'SCHEDULED', scheduledAt: { lte: new Date(untilMs) } },
      orderBy: { scheduledAt: 'asc' },
      take: 50,
    });
    // Skip shows that should have started more than 6 hours ago: nobody is waiting for them any more.
    const staleBefore = this.now() - 6 * 3_600_000;
    return rows.filter((r) => (r.scheduledAt?.getTime() ?? 0) >= staleBefore).map(toEventRecord);
  }

  async nextChampionship(roomId: string, afterMs: number) {
    const e = await this.db.event.findFirst({
      where: { roomId, kind: 'CHAMPIONSHIP', status: 'SCHEDULED', scheduledAt: { gte: new Date(afterMs) } },
      orderBy: { scheduledAt: 'asc' },
    });
    return e && e.scheduledAt ? { eventId: e.id, name: e.name, scheduledAt: e.scheduledAt.getTime() } : null;
  }

  async hostAllowed(userId: string, venueId: string): Promise<boolean> {
    const venue = await this.db.venue.findUnique({ where: { id: venueId }, select: { status: true } });
    if (!venue || venue.status === 'CANCELLED') return false;
    const user = await this.db.venueUser.findFirst({ where: { id: userId, venueId }, select: { id: true } });
    if (user) return true;
    const admin = await this.db.platformAdmin.findUnique({ where: { id: userId }, select: { id: true } });
    return !!admin;
  }

  async loadPack(packKey: string): Promise<PackRecord> {
    const cached = this.packs.get(packKey);
    if (cached && this.now() - cached.at < PACK_TTL_MS) return cached.pack;
    const pack = await this.db.questionPack.findUnique({
      where: { key: packKey },
      include: { questions: { where: { status: 'ACTIVE' } } },
    });
    if (!pack || pack.status !== 'PUBLISHED') throw new Error(`Question pack "${packKey}" is not available`);
    const record: PackRecord = {
      id: pack.id,
      key: pack.key,
      categories: pack.categories as unknown as CategoryDef[],
      questions: pack.questions.map(
        (q): QuestionInput => ({
          id: q.id,
          category: q.category,
          difficulty: q.difficulty,
          type: q.type,
          prompt: q.prompt,
          options: q.options as unknown as { id: string; text: string }[],
          correctOptionId: q.correctOptionId,
          ...(q.explanation ? { explanation: q.explanation } : {}),
          wildcardEligible: q.wildcardEligible,
        }),
      ),
    };
    this.packs.set(packKey, { at: this.now(), pack: record });
    return record;
  }
}
