import { test, expect, request as apiRequest, type APIRequestContext } from '@playwright/test';
import { API_URL, USERS } from '../../fixtures/users';

interface AuthSession {
  accessToken: string;
  refreshToken: string;
  userId: string;
}

interface DoctorCard {
  id: string;
  fullName: string;
}

interface SlotWindow {
  startAt: string;
  endAt: string;
  available: boolean;
}

interface DaySlots {
  date: string;
  dayOfWeek: number;
  slots: SlotWindow[];
}

interface AvailabilityResponse {
  doctorTimezone: string;
  patientTimezone: string;
  slots: DaySlots[];
}

interface AppointmentSummary {
  id: string;
  doctorId?: string | null;
  doctorName?: string;
  patientName?: string;
  specialtyName: string;
  scheduledAt: string;
  status: string;
  symptoms?: string | null;
  awaitingAdminApproval?: boolean;
}

interface QueueItem {
  id: string;
  patientName: string;
  specialtyName?: string | null;
  symptoms: string;
  scheduledAt: string;
  awaitingAdminApproval?: boolean;
}

async function login(ctx: APIRequestContext, role: keyof typeof USERS): Promise<AuthSession> {
  const res = await ctx.post(`${API_URL}/api/v2/auth/login`, {
    data: { email: USERS[role].email, password: USERS[role].password },
  });
  const body = await res.json();
  expect(res.ok(), `${role} login should succeed: ${JSON.stringify(body)}`).toBeTruthy();
  return { accessToken: body.accessToken, refreshToken: body.refreshToken, userId: body.user.id };
}

async function logout(ctx: APIRequestContext, session: AuthSession | undefined) {
  if (!session) return;
  await ctx.post(`${API_URL}/api/v2/auth/logout`, {
    headers: { Authorization: `Bearer ${session.accessToken}`, 'Content-Type': 'application/json' },
    data: { refreshToken: session.refreshToken },
  }).catch(() => null);
}

async function getSeededCardiologist(ctx: APIRequestContext, patient: AuthSession) {
  const res = await ctx.get(`${API_URL}/api/v2/doctors?specialty=cardiology`, {
    headers: { Authorization: `Bearer ${patient.accessToken}` },
  });
  const doctors = (await res.json()) as DoctorCard[];
  expect(res.ok(), 'doctor browse should succeed').toBeTruthy();
  const doctor = doctors.find((d) => /imran/i.test(d.fullName));
  expect(doctor, 'seeded cardiologist should be listed').toBeTruthy();
  return doctor!;
}

async function firstOpenPatientSlot(
  ctx: APIRequestContext,
  patient: AuthSession,
  doctorProfileId: string,
) {
  const res = await ctx.get(`${API_URL}/api/v2/appointments/availability/${doctorProfileId}?days=14`, {
    headers: { Authorization: `Bearer ${patient.accessToken}` },
  });
  expect(res.ok(), 'patient availability should load').toBeTruthy();
  const availability = (await res.json()) as AvailabilityResponse | DaySlots[];
  const days = Array.isArray(availability) ? availability : availability.slots;
  const slot = days.flatMap((day) => day.slots).find((s) => s.available);
  expect(slot, 'doctor should have at least one patient-visible slot').toBeTruthy();
  return slot!;
}

async function firstOpenAdminSlot(
  ctx: APIRequestContext,
  admin: AuthSession,
  doctorUserId: string,
  avoidStartAt?: string,
) {
  const res = await ctx.get(`${API_URL}/api/v2/admin/queue/doctors/${doctorUserId}/availability?days=14`, {
    headers: { Authorization: `Bearer ${admin.accessToken}` },
  });
  expect(res.ok(), 'admin doctor availability should load').toBeTruthy();
  const availability = (await res.json()) as AvailabilityResponse;
  const slot = availability.slots
    .flatMap((day) => day.slots)
    .find((s) => s.available && s.startAt !== avoidStartAt);
  expect(slot, 'admin should have at least one alternate slot').toBeTruthy();
  return slot!;
}

async function bookGuided(
  ctx: APIRequestContext,
  patient: AuthSession,
  scheduledAt: string,
  symptoms: string,
) {
  const res = await ctx.post(`${API_URL}/api/v2/appointments/guided`, {
    headers: { Authorization: `Bearer ${patient.accessToken}`, 'Content-Type': 'application/json' },
    data: {
      specialtyName: 'cardiology',
      scheduledAt,
      symptoms,
      urgency: 'routine',
      timezone: 'Asia/Karachi',
    },
  });
  const body = await res.json();
  expect(res.ok(), `guided booking should succeed: ${JSON.stringify(body)}`).toBeTruthy();
  expect(body.doctorId).toBeNull();
  expect(body.awaitingAdminApproval).toBeTruthy();
  return body as AppointmentSummary;
}

async function appointmentFromList(
  ctx: APIRequestContext,
  session: AuthSession,
  appointmentId: string,
) {
  const res = await ctx.get(`${API_URL}/api/v2/appointments`, {
    headers: { Authorization: `Bearer ${session.accessToken}` },
  });
  const body = (await res.json()) as AppointmentSummary[];
  expect(res.ok(), 'appointment list should load').toBeTruthy();
  const appointment = body.find((item) => item.id === appointmentId);
  expect(appointment, `appointment ${appointmentId} should be visible`).toBeTruthy();
  return appointment!;
}

test('guided booking enters admin queue and allocation updates patient + doctor lists', async () => {
  const ctx = await apiRequest.newContext();
  let patient: AuthSession | undefined;
  let doctor: AuthSession | undefined;
  let admin: AuthSession | undefined;
  try {
    patient = await login(ctx, 'patient');
    doctor = await login(ctx, 'doctor');
    admin = await login(ctx, 'admin');
    const cardiologist = await getSeededCardiologist(ctx, patient);
    const slot = await firstOpenPatientSlot(ctx, patient, cardiologist.id);
    const guided = await bookGuided(
      ctx,
      patient,
      slot.startAt,
      'E2E guided allocation - chest tightness after stairs',
    );

    const queueRes = await ctx.get(`${API_URL}/api/v2/admin/queue/pending`, {
      headers: { Authorization: `Bearer ${admin.accessToken}` },
    });
    const queue = (await queueRes.json()) as QueueItem[];
    expect(queueRes.ok(), 'admin pending queue should load').toBeTruthy();
    expect(queue.some((item) => item.id === guided.id && /Ayesha Siddiqui/i.test(item.patientName))).toBeTruthy();

    const allocateRes = await ctx.post(`${API_URL}/api/v2/admin/queue/${guided.id}/allocate`, {
      headers: { Authorization: `Bearer ${admin.accessToken}`, 'Content-Type': 'application/json' },
      data: {
        doctorUserId: doctor.userId,
        note: 'E2E admin allocation',
      },
    });
    const allocated = (await allocateRes.json()) as AppointmentSummary;
    expect(allocateRes.ok(), `allocation should succeed: ${JSON.stringify(allocated)}`).toBeTruthy();
    expect(allocated.doctorId).toBe(doctor.userId);
    expect(allocated.status).toMatch(/allocated|confirmed/i);
    expect(allocated.awaitingAdminApproval).toBeFalsy();

    const patientView = await appointmentFromList(ctx, patient, guided.id);
    const doctorView = await appointmentFromList(ctx, doctor, guided.id);
    expect(patientView.doctorName).toMatch(/Imran Rashid/i);
    expect(patientView.status).toBe(allocated.status);
    expect(doctorView.patientName).toMatch(/Ayesha Siddiqui/i);
    expect(doctorView.specialtyName).toMatch(/cardiology/i);
    expect(doctorView.status).toBe(allocated.status);
  } finally {
    await Promise.allSettled([
      logout(ctx, patient),
      logout(ctx, doctor),
      logout(ctx, admin),
    ]);
    await ctx.dispose();
  }
});

test('admin reschedule moves a guided booking and both portals show the new slot', async () => {
  const ctx = await apiRequest.newContext();
  let patient: AuthSession | undefined;
  let doctor: AuthSession | undefined;
  let admin: AuthSession | undefined;
  try {
    patient = await login(ctx, 'patient');
    doctor = await login(ctx, 'doctor');
    admin = await login(ctx, 'admin');
    const cardiologist = await getSeededCardiologist(ctx, patient);
    const originalSlot = await firstOpenPatientSlot(ctx, patient, cardiologist.id);
    const guided = await bookGuided(
      ctx,
      patient,
      originalSlot.startAt,
      'E2E guided reschedule - recurring palpitations',
    );
    const newSlot = await firstOpenAdminSlot(ctx, admin, doctor.userId, originalSlot.startAt);

    const rescheduleRes = await ctx.post(`${API_URL}/api/v2/admin/queue/${guided.id}/reschedule`, {
      headers: { Authorization: `Bearer ${admin.accessToken}`, 'Content-Type': 'application/json' },
      data: {
        doctorUserId: doctor.userId,
        scheduledAt: newSlot.startAt,
        note: 'E2E admin reschedule',
      },
    });
    const rescheduled = (await rescheduleRes.json()) as AppointmentSummary;
    expect(rescheduleRes.ok(), `reschedule should succeed: ${JSON.stringify(rescheduled)}`).toBeTruthy();
    expect(rescheduled.doctorId).toBe(doctor.userId);
    expect(rescheduled.scheduledAt).toBe(newSlot.startAt);
    expect(rescheduled.status).toMatch(/allocated|confirmed/i);

    const patientView = await appointmentFromList(ctx, patient, guided.id);
    const doctorView = await appointmentFromList(ctx, doctor, guided.id);
    expect(patientView.scheduledAt).toBe(newSlot.startAt);
    expect(patientView.doctorName).toMatch(/Imran Rashid/i);
    expect(doctorView.scheduledAt).toBe(newSlot.startAt);
    expect(doctorView.patientName).toMatch(/Ayesha Siddiqui/i);

    const activityRes = await ctx.get(`${API_URL}/api/v2/appointments/${guided.id}/activity`, {
      headers: { Authorization: `Bearer ${patient.accessToken}` },
    });
    const activity = (await activityRes.json()) as Array<{ action: string }>;
    expect(activityRes.ok(), 'patient appointment activity should load').toBeTruthy();
    expect(activity.some((row) => row.action === 'admin.appointment.rescheduled')).toBeTruthy();
  } finally {
    await Promise.allSettled([
      logout(ctx, patient),
      logout(ctx, doctor),
      logout(ctx, admin),
    ]);
    await ctx.dispose();
  }
});

test('invalid direct booking slot is rejected without creating a cross-portal appointment', async () => {
  const ctx = await apiRequest.newContext();
  let patient: AuthSession | undefined;
  try {
    patient = await login(ctx, 'patient');
    const cardiologist = await getSeededCardiologist(ctx, patient);
    const invalidSoon = new Date(Date.now() + 60_000).toISOString();
    const beforeRes = await ctx.get(`${API_URL}/api/v2/appointments`, {
      headers: { Authorization: `Bearer ${patient.accessToken}` },
    });
    const before = (await beforeRes.json()) as AppointmentSummary[];
    expect(beforeRes.ok(), 'precondition appointment list should load').toBeTruthy();

    const res = await ctx.post(`${API_URL}/api/v2/appointments`, {
      headers: { Authorization: `Bearer ${patient.accessToken}`, 'Content-Type': 'application/json' },
      data: {
        doctorId: cardiologist.id,
        specialtyName: 'cardiology',
        scheduledAt: invalidSoon,
        symptoms: 'E2E invalid negative booking - too soon',
        urgency: 'routine',
      },
    });
    const body = await res.json();
    expect(res.ok()).toBeFalsy();
    expect([400, 409]).toContain(res.status());
    expect(JSON.stringify(body)).toMatch(/slot|future|available|schedule|least/i);

    const afterRes = await ctx.get(`${API_URL}/api/v2/appointments`, {
      headers: { Authorization: `Bearer ${patient.accessToken}` },
    });
    const after = (await afterRes.json()) as AppointmentSummary[];
    expect(afterRes.ok(), 'postcondition appointment list should load').toBeTruthy();
    expect(after.length).toBe(before.length);
  } finally {
    await logout(ctx, patient);
    await ctx.dispose();
  }
});
