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

function setupSession(db: Database.Database, participantCount: number) {
  const session = db
    .prepare("INSERT INTO sessions (code, city, status) VALUES (?, ?, 'active')")
    .run(`S${Date.now()}`, 'Teststadt');
  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()}`);
    participantIds.push(Number(p.lastInsertRowid));
  }

  const r = db.prepare("INSERT INTO restaurants (name) VALUES (?)").run('Test Restaurant');
  const restaurantId = Number(r.lastInsertRowid);

  return { sessionId, participantIds, restaurantId };
}

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('evaluateMatch', () => {
  let db: Database.Database;

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

  it('matches when 2 active participants both vote yes', () => {
    const { sessionId, participantIds, restaurantId } = setupSession(db, 2);
    addSwipe(db, sessionId, participantIds[0], restaurantId, 'yes');
    addSwipe(db, sessionId, participantIds[1], restaurantId, 'yes');

    const result = evaluateMatch(db, sessionId, restaurantId);
    expect(result.isMatch).toBe(true);
    expect(result.matchPercentage).toBe(100);
  });

  it('does not match when only 1 of 2 participants votes yes', () => {
    const { sessionId, participantIds, restaurantId } = setupSession(db, 2);
    addSwipe(db, sessionId, participantIds[0], restaurantId, 'yes');
    addSwipe(db, sessionId, participantIds[1], restaurantId, 'no');

    const result = evaluateMatch(db, sessionId, restaurantId);
    expect(result.isMatch).toBe(false);
    expect(result.yesCount).toBe(1);
  });

  it('matches for 3 participants with 2/3 yes', () => {
    const { sessionId, participantIds, restaurantId } = setupSession(db, 3);
    addSwipe(db, sessionId, participantIds[0], restaurantId, 'yes');
    addSwipe(db, sessionId, participantIds[1], restaurantId, 'yes');
    addSwipe(db, sessionId, participantIds[2], restaurantId, 'no');

    const result = evaluateMatch(db, sessionId, restaurantId);
    expect(result.isMatch).toBe(true);
    expect(result.matchPercentage).toBeCloseTo(66.67, 1);
  });

  it('does not match for 3 participants with 1/3 yes', () => {
    const { sessionId, participantIds, restaurantId } = setupSession(db, 3);
    addSwipe(db, sessionId, participantIds[0], restaurantId, 'yes');
    addSwipe(db, sessionId, participantIds[1], restaurantId, 'no');
    addSwipe(db, sessionId, participantIds[2], restaurantId, 'no');

    const result = evaluateMatch(db, sessionId, restaurantId);
    expect(result.isMatch).toBe(false);
  });

  it('never matches a vetoed restaurant', () => {
    const { sessionId, participantIds, restaurantId } = setupSession(db, 2);
    addVeto(db, sessionId, participantIds[0], restaurantId);
    addSwipe(db, sessionId, participantIds[0], restaurantId, 'yes');
    addSwipe(db, sessionId, participantIds[1], restaurantId, 'yes');

    const result = evaluateMatch(db, sessionId, restaurantId);
    expect(result.isMatch).toBe(false);
  });

  it('ignores inactive participants in the active count', () => {
    const { sessionId, participantIds, restaurantId } = setupSession(db, 3);
    db.prepare('UPDATE participants SET is_active = 0 WHERE id = ?').run(participantIds[2]);

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

    const result = evaluateMatch(db, sessionId, restaurantId);
    expect(result.activeParticipantCount).toBe(2);
    expect(result.isMatch).toBe(true);
  });

  it('does not match before all active participants have voted (quorum)', () => {
    const { sessionId, participantIds, restaurantId } = setupSession(db, 3);
    addSwipe(db, sessionId, participantIds[0], restaurantId, 'yes');
    addSwipe(db, sessionId, participantIds[1], restaurantId, 'yes');

    // Zwei Ja-Stimmen, aber der dritte hat noch nicht abgestimmt -> kein Match.
    const pending = evaluateMatch(db, sessionId, restaurantId);
    expect(pending.isMatch).toBe(false);
  });

  it('matches once the last participant votes no (2 yes + 1 no)', () => {
    const { sessionId, participantIds, restaurantId } = setupSession(db, 3);
    addSwipe(db, sessionId, participantIds[0], restaurantId, 'yes');
    addSwipe(db, sessionId, participantIds[1], restaurantId, 'yes');

    const before = evaluateMatch(db, sessionId, restaurantId);
    expect(before.isMatch).toBe(false);

    addSwipe(db, sessionId, participantIds[2], restaurantId, 'no');
    const after = evaluateMatch(db, sessionId, restaurantId);
    expect(after.isMatch).toBe(true);
    expect(after.matchPercentage).toBeCloseTo(66.67, 1);
  });
});
