import { faker } from '@faker-js/faker';
import { request, Page, APIRequestContext } from '@playwright/test';

import { VITALS_DATA_ELEMENT_IDS } from '@tamanu/constants/surveys';

import { constructFacilityUrl } from './navigation';
import { getAuthTokenFromLocalStorage, getItemFromLocalStorage } from './localStorage';
import type { Patient, User } from '@tamanu/database';
import { generateNHN } from './generateNewPatient';
import { testData } from './testData';

export const createApiContext = async ({ page }: { page: Page }) => {
  const token = await getAuthTokenFromLocalStorage(page);

  return request.newContext({
    baseURL: constructFacilityUrl('/'),
    extraHTTPHeaders: {
      Authorization: `Bearer ${token}`,
    },
  });
};

export const getUser = async (api: APIRequestContext): Promise<User> => {
  const userUrl = constructFacilityUrl('/api/user/me');
  const user = await api.get(userUrl);
  return user.json();
};

// Resolves the logged-in facility from browser storage. Kept separate from the API helpers so
// they stay independent of the browser context: callers that have a page resolve the facilityId
// here and pass it in, while callers that already hold it (e.g. fixtures) pass it directly.
export const getFacilityId = (page: Page): Promise<string> =>
  getItemFromLocalStorage(page, 'facilityId');

// Returns every practitioner the "Discontinued by" / clinician suggesters offer. Maps to
// { id, name } where name is the user's display name.
export const getPractitioners = async (
  api: APIRequestContext,
): Promise<Array<{ id: string; name: string }>> => {
  const url = constructFacilityUrl('/api/suggestions/practitioner/all');
  const response = await api.get(url);
  if (!response.ok()) {
    throw new Error(`Failed to fetch practitioners: ${response.status()}`);
  }
  return response.json();
};

// Stable reference-data id for the "Other" note type (seeded by migration on every server).
const ENCOUNTER_NOTE_TYPE_ID = 'notetype-other';

// Bulk-creates plain encounter notes via the API. Used to exercise the chunked PDF pipeline,
// where the encounter record is rendered in slices once the note count grows large. Requests run
// with bounded concurrency so seeding thousands of notes stays reasonably quick without
// hammering the server.
export const createEncounterNotes = async (
  api: APIRequestContext,
  encounterId: string,
  authorId: string,
  count: number,
  concurrency = 20,
): Promise<void> => {
  const notesUrl = constructFacilityUrl('/api/notes');
  const date = new Date().toISOString().replace('T', ' ').substring(0, 19);

  let nextIndex = 0;
  const createNotesFromQueue = async (): Promise<void> => {
    for (let i = nextIndex++; i < count; i = nextIndex++) {
      const response = await api.post(notesUrl, {
        data: {
          recordId: encounterId,
          recordType: 'Encounter',
          noteTypeId: ENCOUNTER_NOTE_TYPE_ID,
          authorId,
          date,
          content: `Load-test note ${i + 1}`,
        },
      });
      if (!response.ok()) {
        throw new Error(
          `Failed to create note ${i + 1}: ${response.status()} ${await response.text()}`,
        );
      }
    }
  };

  await Promise.all(Array.from({ length: Math.min(concurrency, count) }, createNotesFromQueue));
};

export const createPatient = async (
  api: APIRequestContext,
  page: Page,
  options: Partial<{
    dateOfBirth: Date;
  }> = {},
): Promise<Patient> => {
  const patientUrl = constructFacilityUrl('/api/patient');

  const facilityId = await getItemFromLocalStorage(page, 'facilityId');
  const user = await getUser(api);

  const patientData = {
    birthFacilityId: null,
    dateOfBirth: options.dateOfBirth || faker.date.birthdate(),
    displayId: generateNHN(),
    facilityId,
    firstName: faker.person.firstName(),
    lastName: faker.person.lastName(),
    patientRegistryType: 'new_patient',
    registeredById: user.id,
    sex: faker.person.sex(),
    villageId: testData.villageId,
    culturalName: faker.person.middleName(),
  };

  const response = await api.post(patientUrl, {
    data: patientData,
  });

  if (!response.ok()) {
    const errorText = await response.text();
    console.error('Failed to create patient:', response.status(), errorText);
    throw new Error(`Failed to create patient: ${response.status()} ${errorText}`);
  }


  return response.json();
};

// TODO: swap these functions to use the new fakeRequests in fakeData package when it's merged
export const createHospitalAdmissionEncounterViaAPI = async (
  api: APIRequestContext,
  patientId: string,
  overrides: Partial<{
    departmentId: string;
    encounterType: string;
    examinerId: string;
    facilityId: string;
    locationId: string;
    patientBillingTypeId: string;
    startDate: string;
  }> = {},
) => {
  const encounterUrl = constructFacilityUrl('/api/encounter');
  const user = await getUser(api);


  const encounterData = {
    departmentId: testData.departmentId,
    encounterType: testData.encounterType,
    examinerId: user.id,
    locationId: testData.locationId,
    patientBillingTypeId: testData.patientBillingTypeId,
    patientId: testData.patientId || patientId  ,
    startDate: new Date().toISOString().replace('T', ' ').substring(0, 19),
    ...overrides,
    dietIds: JSON.stringify(['diet-Carb-Controlled', 'diet-Citrusfree']),
  };

  const response = await api.post(encounterUrl, {
    data: encounterData,
  });

  if (!response.ok()) {
    const errorText = await response.text();
    console.error('Failed to create hospital admission encounter:', response.status(), errorText);
    throw new Error(
      `Failed to create hospital admission encounter: ${response.status()} ${errorText}`,
    );
  }

  return response.json();
};

// Discharge = give the encounter an end date. An invoice can only be finalised once its encounter
// has been discharged, so this is the arrange step for the finalisation journey.
export const dischargeEncounterViaApi = async (
  api: APIRequestContext,
  encounterId: string,
  endDate: string = new Date().toISOString().replace('T', ' ').substring(0, 19),
) => {
  const encounterUrl = constructFacilityUrl(`/api/encounter/${encounterId}`);
  const response = await api.put(encounterUrl, { data: { endDate } });

  if (!response.ok()) {
    const errorText = await response.text();
    throw new Error(`Failed to discharge encounter: ${response.status()} ${errorText}`);
  }

  return response.json();
};
// TODO: swap these functions to use the new fakeRequests in fakeData package when it's merged
export const createTriageEncounterViaApi = async (
  api: APIRequestContext,
  page: Page,
  patientId: string,
  overrides: Partial<{
    chiefComplaintId: string;
    facilityId: string;
    locationId: string;
    practitionerId: string;
    score: string;
    startDate: string;
    triageTime: string;
    vitals: any;
  }> = {},
) => {
  const triageUrl = constructFacilityUrl('/api/triage');
  const user = await getUser(api);
  const facilityId = await getItemFromLocalStorage(page, 'facilityId');

  const triageData = {
    chiefComplaintId: 'triage-Abdominalpaindistension',
    facilityId,
    locationId: 'location-EDBed1-tamanu',
    patientId,
    practitionerId: user.id,
    score: '1',
    startDate: new Date().toISOString().replace('T', ' ').substring(0, 19),
    triageTime: new Date().toISOString().replace('T', ' ').substring(0, 19),
    vitals: null,
    ...overrides,
  };

  const response = await api.post(triageUrl, {
    data: triageData,
  });

  if (!response.ok()) {
    const errorText = await response.text();
    console.error('Failed to create triage encounter:', response.status(), errorText);
    throw new Error(`Failed to create triage encounter: ${response.status()} ${errorText}`);
  }

  return response.json();
};
// TODO: swap these functions to use the new fakeRequests in fakeData package when it's merged
export const createClinicEncounterViaApi = async (
  api: APIRequestContext,
  page: Page,
  patientId: string,
  overrides: Partial<{
    departmentId: string;
    encounterType: string;
    examinerId: string;
    facilityId: string;
    locationId: string;
    startDate: string;
  }> = {},
) => {
  const encounterUrl = constructFacilityUrl('/api/encounter');
  const user = await getUser(api);
  const facilityId = await getItemFromLocalStorage(page, 'facilityId');

  const encounterData = {
    departmentId: testData.departmentId,
    encounterType: 'clinic',
    examinerId: user.id,
    facilityId,
    locationId: testData.locationId,
    patientId,
    startDate: new Date().toISOString().replace('T', ' ').substring(0, 19),
    ...overrides,
  };

  const response = await api.post(encounterUrl, {
    data: encounterData,
  });

  if (!response.ok()) {
    const errorText = await response.text();
    console.error('Failed to create clinic encounter:', response.status(), errorText);
    throw new Error(`Failed to create clinic encounter: ${response.status()} ${errorText}`);
  }

  return response.json();
};

/**
 * Drugs from reference data, for tests that need a known medication name or several distinct
 * medications (e.g. one sent to pharmacy on discharge and one left behind).
 *
 * When a facilityId is given the suggester excludes drugs unavailable at that facility, matching
 * the real prescribing UI. Otherwise the row click in the medication table is a no-op (the pane
 * ignores clicks on unavailable medications) and the details modal never opens.
 */
export const getDrugSuggestions = async (
  api: APIRequestContext,
  count = 1,
  facilityId?: string,
): Promise<{ id: string; name: string }[]> => {
  const facilityParam = facilityId ? `&facilityId=${facilityId}` : '';
  const suggestUrl = constructFacilityUrl(`/api/suggestions/drug?count=${count}${facilityParam}`);
  const suggestResponse = await api.get(suggestUrl);
  if (!suggestResponse.ok()) {
    throw new Error(`Failed to fetch drug suggestions: ${suggestResponse.status()}`);
  }
  const drugs = await suggestResponse.json();
  if (drugs.length < count) {
    throw new Error(
      `Expected ${count} drugs in reference data, found ${drugs.length}. Check provisioning.`,
    );
  }
  return drugs;
};

const fetchFirstDrugId = async (api: APIRequestContext, facilityId?: string): Promise<string> =>
  (await getDrugSuggestions(api, 1, facilityId))[0].id;

export const createEncounterPrescriptionViaApi = async (
  api: APIRequestContext,
  encounterId: string,
  facilityId?: string,
  overrides: Partial<{
    medicationId: string;
    route: string;
    doseAmount: number;
    frequency: string;
  }> = {},
) => {
  const user = await getUser(api);
  const medicationId = overrides.medicationId ?? (await fetchFirstDrugId(api, facilityId));

  const now = new Date();
  const dateString = now.toISOString().substring(0, 10);
  const datetimeString = now.toISOString().replace('T', ' ').substring(0, 19);

  const prescriptionData = {
    medicationId,
    prescriberId: user.id,
    date: dateString,
    startDate: datetimeString,
    route: 'oral',
    doseAmount: 1,
    frequency: 'Immediately',
    ...overrides,
  };

  const url = constructFacilityUrl(`/api/medication/encounterPrescription/${encounterId}`);
  const response = await api.post(url, { data: prescriptionData });

  if (!response.ok()) {
    const errorText = await response.text();
    throw new Error(`Failed to create prescription: ${response.status()} ${errorText}`);
  }

  return response.json();
};

export const createPatientOngoingPrescriptionViaApi = async (
  api: APIRequestContext,
  patientId: string,
  facilityId?: string,
  overrides: Partial<{
    medicationId: string;
    route: string;
    doseAmount: number;
    units: string;
    frequency: string;
  }> = {},
) => {
  const user = await getUser(api);
  const medicationId = await fetchFirstDrugId(api, facilityId);

  const now = new Date();
  const dateString = now.toISOString().substring(0, 10);
  const datetimeString = now.toISOString().replace('T', ' ').substring(0, 19);

  const prescriptionData = {
    medicationId,
    prescriberId: user.id,
    date: dateString,
    startDate: datetimeString,
    route: 'oral',
    doseAmount: 1,
    units: 'mg',
    frequency: 'Immediately',
    isOngoing: true,
    ...overrides,
  };

  const url = constructFacilityUrl(`/api/medication/patientOngoingPrescription/${patientId}`);
  const response = await api.post(url, { data: prescriptionData });

  if (!response.ok()) {
    const errorText = await response.text();
    throw new Error(`Failed to create ongoing prescription: ${response.status()} ${errorText}`);
  }

  return response.json();
};

export const createPharmacyOrderViaApi = async (
  api: APIRequestContext,
  page: Page,
  encounterId: string,
  prescriptionId: string,
) => {
  const user = await getUser(api);
  const facilityId = await getItemFromLocalStorage(page, 'facilityId');

  const url = constructFacilityUrl(`/api/encounter/${encounterId}/pharmacyOrder`);
  const response = await api.post(url, {
    data: {
      orderingClinicianId: user.id,
      comments: '',
      isDischargePrescription: false,
      facilityId,
      pharmacyOrderPrescriptions: [{ prescriptionId, quantity: 1, repeats: 0 }],
    },
  });

  if (!response.ok()) {
    const errorText = await response.text();
    throw new Error(`Failed to create pharmacy order: ${response.status()} ${errorText}`);
  }

  return response.json();
};

// Creates one vitals survey response (a "reading") at a given recorded date/time, so tests can
// seed a spread of readings across several days to exercise the vitals chart's time-range filter.
export const createVitalsReadingViaApi = async (
  api: APIRequestContext,
  page: Page,
  encounterId: string,
  patientId: string,
  recordedDate: string,
  overrides: Partial<{
    temperature: number;
  }> = {},
) => {
  const facilityId = await getItemFromLocalStorage(page, 'facilityId');

  const surveyUrl = constructFacilityUrl('/api/survey/vitals');
  const surveyResponse = await api.get(surveyUrl);
  if (!surveyResponse.ok()) {
    throw new Error(`Failed to fetch vitals survey: ${surveyResponse.status()}`);
  }
  const { id: surveyId } = await surveyResponse.json();

  const surveyResponseUrl = constructFacilityUrl('/api/surveyResponse');
  const response = await api.post(surveyResponseUrl, {
    data: {
      surveyId,
      encounterId,
      patientId,
      facilityId,
      startTime: recordedDate,
      endTime: recordedDate,
      answers: {
        [VITALS_DATA_ELEMENT_IDS.dateRecorded]: recordedDate,
        [VITALS_DATA_ELEMENT_IDS.temperature]: overrides.temperature ?? 36.8,
      },
    },
  });

  if (!response.ok()) {
    const errorText = await response.text();
    throw new Error(`Failed to create vitals reading: ${response.status()} ${errorText}`);
  }

  return response.json();
};

export const recordPatientDeathViaApi = async (
  api: APIRequestContext,
  page: Page,
  patientId: string,
) => {
  const facilityId = await getItemFromLocalStorage(page, 'facilityId');
  const user = await getUser(api);

  // Verify patient exists first
  const verifyPatientUrl = constructFacilityUrl(`/api/patient/${patientId}`);

  const verifyResponse = await api.get(verifyPatientUrl);

  if (!verifyResponse.ok()) {
    throw new Error(`Patient ${patientId} not found. Please ensure patient is created first.`);
  }

  const apiDeathUrl = constructFacilityUrl(`/api/patient/${patientId}/death`);

  const deathData = {
    clinicianId: user.id,
    facilityId,
    timeOfDeath: new Date().toISOString(),
    manner: 'Disease',
    outsideHealthFacility: false,
    isPartialWorkflow: true,
  };

  const response = await api.post(apiDeathUrl, {
    data: deathData,
  });

  if (!response.ok()) {
    const responseText = await response.text();
    console.error('Response status:', response.status());
    console.error('Response text:', responseText);
    throw new Error(`Failed to record patient death: ${response.statusText()} - ${responseText}`);
  }

  return response.json();
};
