import { describe, it, expect, beforeEach } from 'vitest';
import type Database from 'better-sqlite3';
import { computeRanking } from '@/lib/ranking';
import { createTestDb } from '@/lib/test-helpers';

let seq = 0;
function setupSession(db: Database.Database, participantCount: number) {
  const session = db
    .prepare("INSERT INTO sessions (code, city, status) VALUES (?, 'Teststadt', 'active')")
    .run(`R${Date.now()}-${seq++}`);
  const sessionId = Number(session.lastInsertRowid);

  const participantIds: number[] = [];
  for (let i = 0; i < participantCount; i++) {
    const p = db
      .prepare('INSERT INTO participants (session_id, anonymous_id, is_active) VALUES (?, ?, 1)')
      .run(sessionId, `anon-${i}-${Date.now()}-${seq++}`);
    participantIds.push(Number(p.lastInsertRowid));
  }

  return { sessionId, participantIds };
}

function addRestaurant(db: Database.Database, name: string): number {
  return Number(db.prepare('INSERT INTO restaurants (name, cuisine) VALUES (?, ?)').run(name, 'italian').lastInsertRowid);
}

function addSwipe(db: Database.Database, sessionId: number, participantId: number, restaurantId: number, direction: 'yes' | 'no') {
  db.prepare('INSERT INTO swipes (session_id, participant_id, restaurant_id, direction) VALUES (?, ?, ?, ?)')
    .run(sessionId, participantId, restaurantId, direction);
}

function addVeto(db: Database.Database, sessionId: number, participantId: number, restaurantId: number) {
  db.prepare('INSERT INTO vetos (session_id, participant_id, restaurant_id) VALUES (?, ?, ?)')
    .run(sessionId, participantId, restaurantId);
}

describe('computeRanking', () => {
  let db: Database.Database;

  beforeEach(() => {
    db = createTestDb();
  });

  it('sorts restaurants by yes count descending', () => {
    const { sessionId, participantIds } = setupSession(db, 3);
    const a = addRestaurant(db, 'Restaurant A');
    const b = addRestaurant(db, 'Restaurant B');
    const c = addRestaurant(db, 'Restaurant C');

    addSwipe(db, sessionId, participantIds[0], a, 'yes');
    addSwipe(db, sessionId, participantIds[1], a, 'yes');
    addSwipe(db, sessionId, participantIds[2], a, 'no');

    addSwipe(db, sessionId, participantIds[0], b, 'yes');

    addSwipe(db, sessionId, participantIds[0], c, 'yes');
    addSwipe(db, sessionId, participantIds[1], c, 'yes');
    addSwipe(db, sessionId, participantIds[2], c, 'yes');

    const ranking = computeRanking(db, sessionId);
    expect(ranking.map((r) => r.restaurant_id)).toEqual([c, a, b]);
    expect(ranking[0].yes_count).toBe(3);
    expect(ranking[1].yes_count).toBe(2);
    expect(ranking[2].yes_count).toBe(1);
  });

  it('excludes vetoed restaurants', () => {
    const { sessionId, participantIds } = setupSession(db, 2);
    const a = addRestaurant(db, 'Restaurant A');
    const b = addRestaurant(db, 'Restaurant B');

    addSwipe(db, sessionId, participantIds[0], a, 'yes');
    addSwipe(db, sessionId, participantIds[1], a, 'yes');
    addSwipe(db, sessionId, participantIds[0], b, 'yes');
    addVeto(db, sessionId, participantIds[1], b);

    const ranking = computeRanking(db, sessionId);
    expect(ranking.map((r) => r.restaurant_id)).toEqual([a]);
  });

  it('excludes the winner when requested', () => {
    const { sessionId, participantIds } = setupSession(db, 3);
    const a = addRestaurant(db, 'Restaurant A');
    const b = addRestaurant(db, 'Restaurant B');

    addSwipe(db, sessionId, participantIds[0], a, 'yes');
    addSwipe(db, sessionId, participantIds[1], a, 'yes');
    addSwipe(db, sessionId, participantIds[2], a, 'no');
    addSwipe(db, sessionId, participantIds[0], b, 'yes');

    const ranking = computeRanking(db, sessionId, a);
    expect(ranking.map((r) => r.restaurant_id)).toEqual([b]);
  });

  it('only includes restaurants with at least one yes', () => {
    const { sessionId, participantIds } = setupSession(db, 2);
    const a = addRestaurant(db, 'Restaurant A');
    const b = addRestaurant(db, 'Restaurant B');

    addSwipe(db, sessionId, participantIds[0], a, 'yes');
    addSwipe(db, sessionId, participantIds[1], b, 'no');

    const ranking = computeRanking(db, sessionId);
    expect(ranking.map((r) => r.restaurant_id)).toEqual([a]);
  });

  it('tie-breaks by fewer no votes, then alphabetically', () => {
    const { sessionId, participantIds } = setupSession(db, 3);
    const bName = addRestaurant(db, 'Bravo');
    const aName = addRestaurant(db, 'Alpha');

    addSwipe(db, sessionId, participantIds[0], bName, 'yes');
    addSwipe(db, sessionId, participantIds[0], aName, 'yes');

    const ranking = computeRanking(db, sessionId);
    expect(ranking.map((r) => r.restaurant_id)).toEqual([aName, bName]);
  });
});
