import 'reflect-metadata';
import { DataSource } from 'typeorm';
import { AuditSlotsService } from '../services/audit-slots.service';

let fail = 0;
const ok = (name: string, cond: boolean, extra = '') => { if (!cond) fail++; console.log(cond ? 'PASS' : 'FAIL', name, cond ? '' : extra); };

(async () => {
  const ds = new DataSource({ type: 'mysql', host: '127.0.0.1', port: 3307, username: 't', password: 't', database: 'schemedb', extra: { connectionLimit: 15 } });
  await ds.initialize();
  const svc = new AuditSlotsService(ds);
  const reset = () => ds.query('DELETE FROM audit_schedule_rows').then(() => ds.query('DELETE FROM audit_schedules')).then(() => ds.query('DELETE FROM audit_requests'));
  // pick a window date that is "tomorrow" so no "time passed" noise, and skip the closed date
  let D = svc.addDays(svc.now().date, 1); if (D === '2026-10-16') D = svc.addDays(D, 1);
  const cell = async (date: string, t: string, o: any = {}) => (await svc.getDaySlots(date, o)).sessions.flatMap(s => s.slots).find(c => c.time === t)!;
  const addReq = (company: string, date: string, time: string, status = 'SUBMITTED', rowId: number | null = null) =>
    ds.query('INSERT INTO audit_requests (company_name, proposed_date, proposed_time, status, audit_schedule_row_id) VALUES (?,?,?,?,?)', [company, date, time, status, rowId]).then((r: any) => r.insertId as number);

  await reset();
  ok('empty day: 10:00 has 2 free', (await cell(D, '10:00')).free === 2);

  // 1) ABC books 10:00 → 1 left for another auditor (the user's scenario)
  const r1 = await addReq('ABC Company', D, '10:00:00');
  ok('after 1 booking: 10:00 has 1 free (limited)', (await cell(D, '10:00')).free === 1 && (await cell(D, '10:00')).status === 'limited');
  ok('10:30 still has 1 free (gap 60)', (await cell(D, '10:30')).free === 1);
  // 2) second company books 10:00 → fully booked
  const r2 = await addReq('Second Company', D, '10:00:00');
  const c = await cell(D, '10:00');
  ok('after 2 bookings: 10:00 closed "Fully booked"', c.free === 0 && c.reason === 'Fully booked');
  ok('10:30 closed "Too close"', (await cell(D, '10:30')).reason === 'Too close to another audit');
  ok('11:00 open again', (await cell(D, '11:00')).free === 2);

  // 3) rejected / cancelled requests free the slot
  await ds.query("UPDATE audit_requests SET status='CANCELLED' WHERE id=?", [r2]);
  ok('cancelled request reopens 10:00 (1 free)', (await cell(D, '10:00')).free === 1);
  await ds.query("UPDATE audit_requests SET status='REJECTED' WHERE id=?", [r1]);
  ok('rejected request reopens 10:00 (2 free)', (await cell(D, '10:00')).free === 2);

  // 4) scheduled request: the ROW is the truth. Cancel the row → slot opens although request stays SCHEDULED
  await reset();
  const sch = (await ds.query("INSERT INTO audit_schedules (schedule_date) VALUES (?)", [D]) as any).insertId;
  const row = (await ds.query("INSERT INTO audit_schedule_rows (schedule_id, audit_time, audit_request_id) VALUES (?, '10:00:00', NULL)", [sch]) as any).insertId;
  const rq = await addReq('Scheduled Co', D, '10:00:00', 'SCHEDULED', row);
  await ds.query('UPDATE audit_schedule_rows SET audit_request_id=? WHERE id=?', [rq, row]);
  ok('scheduled request counted ONCE (not request+row)', (await cell(D, '10:00')).free === 1);
  await ds.query("UPDATE audit_schedule_rows SET status='CANCELLED' WHERE id=?", [row]);
  ok('coordinator cancels row → 10:00 fully open again', (await cell(D, '10:00')).free === 2);
  ok('request still SCHEDULED in db (proves we derive from row)', (await ds.query('SELECT status FROM audit_requests WHERE id=?', [rq]))[0].status === 'SCHEDULED');
  // 5) reschedule: row moves to 14:00 → 10:00 frees, 14:00 takes it
  await ds.query("UPDATE audit_schedule_rows SET status='PENDING', audit_time='14:00:00' WHERE id=?", [row]);
  ok('rescheduled row frees 10:00', (await cell(D, '10:00')).free === 2);
  ok('rescheduled row occupies 14:00', (await cell(D, '14:00')).free === 1);
  // 6) standalone row (no request) counts; cancelled schedule does not
  await ds.query("INSERT INTO audit_schedule_rows (schedule_id, audit_time) VALUES (?, '14:00:00')", [sch]);
  ok('standalone row counts (14:00 → 0 free)', (await cell(D, '14:00')).free === 0);
  await ds.query("UPDATE audit_schedules SET status='CANCELLED' WHERE id=?", [sch]);
  ok('cancelled schedule frees everything', (await cell(D, '14:00')).free === 2);

  // 7) excludeRequestId (edit own request) + holds (batch siblings)
  await reset();
  const mine = await addReq('Mine', D, '10:00:00');
  await addReq('Other', D, '10:00:00');
  ok('editing: own booking counts normally', (await cell(D, '10:00')).free === 0);
  ok('editing: exclude_request_id ignores self', (await cell(D, '10:00', { excludeRequestId: mine })).free === 1);
  ok('holds from sibling cards reduce capacity', (await cell(D, '11:00', { holds: ['11:00', '11:00'] })).free === 0);

  // 8) validation: grid, lunch, window, closed
  const rej = async (fn: () => Promise<any>) => { try { await fn(); return null; } catch (e: any) { return e; } };
  ok('rejects 10:15 (off-grid)', !!(await rej(() => svc.assertSlotAvailable(D, '10:15'))));
  ok('rejects 13:00 (lunch)', !!(await rej(() => svc.assertSlotAvailable(D, '13:00'))));
  ok('rejects 18:00', !!(await rej(() => svc.assertSlotAvailable(D, '18:00'))));
  ok('rejects past date', !!(await rej(() => svc.assertSlotAvailable('2020-01-01', '10:00'))));
  ok('rejects date beyond window', !!(await rej(() => svc.assertSlotAvailable(svc.addDays(svc.now().date, 30), '10:00'))));
  const taken = await rej(() => svc.assertSlotAvailable(D, '10:00'));
  ok('full slot → 409 with alternatives', taken?.getStatus?.() === 409 && taken.getResponse().alternatives.length > 0, JSON.stringify(taken?.getResponse?.()));
  console.log('   message:', taken?.getResponse?.().message);
  ok('valid slot returns HH:MM:00', (await svc.assertSlotAvailable(D, '11:00')) === '11:00:00');
  ok('accepts HH:MM:SS input', (await svc.assertSlotAvailable(D, '11:00:00')) === '11:00:00');

  // 9) batch pre-check: 3 clients, same 12:00 → third must fail up-front, nothing written
  await reset();
  const batch = [
    { company_name: 'A', proposed_date: D, proposed_time: '12:00:00' },
    { company_name: 'B', proposed_date: D, proposed_time: '12:00:00' },
    { company_name: 'C', proposed_date: D, proposed_time: '12:00:00' },
  ];
  const be = await rej(() => svc.assertBatchAvailable(batch));
  ok('batch: 3rd client at same time rejected up-front', !!be && /Client 3 \(C\)/.test(be.getResponse().message), String(be?.message));
  ok('batch: 2 clients at same time accepted', (await rej(() => svc.assertBatchAvailable(batch.slice(0, 2)))) === null);

  // 10) CONCURRENCY — 8 users race for the last 2 seats at 15:00 → exactly 2 succeed
  await reset();
  const attempt = (i: number) => svc.withDateLock(D, async (m) => {
    const t = await svc.assertSlotAvailable(D, '15:00', m);
    await new Promise(r => setTimeout(r, 40));             // widen the race window
    await m.query('INSERT INTO audit_requests (company_name, proposed_date, proposed_time) VALUES (?,?,?)', ['Racer ' + i, D, t]);
    return i;
  }).then(() => 'ok', (e) => e?.getStatus?.() === 409 ? 'conflict' : 'error:' + e?.message);
  const results = await Promise.all(Array.from({ length: 8 }, (_, i) => attempt(i)));
  const wins = results.filter(r => r === 'ok').length;
  const rowsAt = (await ds.query("SELECT COUNT(*) n FROM audit_requests WHERE proposed_date=? AND proposed_time='15:00:00'", [D]))[0].n;
  ok('race: exactly 2 of 8 succeed', wins === 2 && Number(rowsAt) === 2, JSON.stringify(results));
  ok('race: others get a clean 409', results.filter(r => r === 'conflict').length === 6, JSON.stringify(results));
  // lock is released: another attempt works immediately (different slot)
  ok('lock released after use', (await svc.withDateLock(D, async m => svc.assertSlotAvailable(D, '16:00', m))) === '16:00:00');

  // 11) summaries
  const sum = await svc.getDateSummaries();
  ok('summaries: window has 4 dates & flags closed date', sum.dates.length === 4 && sum.max_per_day === 10);
  console.log('   summary:', JSON.stringify(sum.dates.map(d => [d.date, d.booked, d.free_slots, d.status])));

  await reset(); await ds.destroy();
  console.log(fail ? `\n${fail} FAILED` : '\nALL INTEGRATION CHECKS PASSED');
  process.exit(fail ? 1 : 0);
})().catch(e => { console.error('CRASH', e); process.exit(2); });
