import { sleep } from '@/utils';
import { ApiError } from '@/lib/api/errors';
import type { PageParams, Paginated } from '@/lib/api/types';
import { MOCK_FOREIGN_PATIENT_ID } from '../constants';
import type {
  CreateVisitNoteRequest,
  FamilyCareRecord,
  Medication,
  PatientRecordsApi,
  RecordAccess,
  RoutineItem,
  UpdateFamilyRecordRequest,
  VisitNote,
  WriteVisitNoteResult,
} from '../types';

/**
 * In-memory `PatientRecordsApi` — **the primary implementation this phase** (the nurse-authored visit-note
 * history/append are real b14, but the family-owned medications/routine/tasks record + the access check are
 * REQ-027 gaps — see `constants.ts`).
 *
 * The store is **patient-scoped** and lazily seeds a coherent default the first time any patient is read, so
 * every E2 record viewer has content and every state is demoable:
 *  - a **default family record** (medications/routine/tasks the customer edits);
 *  - a **multi-nurse continuity history** (two prior notes from *different* nurses — proving the history
 *    persists across nurse changes; a nurse append prepends to the SAME patient's history);
 *  - a **foreign-patient access-denied** path (`MOCK_FOREIGN_PATIENT_ID` → `canView: false` + a `403` on
 *    every read) so the non-leaking access-denied card is demoable.
 *
 * Money-free domain; clinical text is fixture data (never logged). Patient ids align with the f8 booking
 * snapshots (patient 905 on the completed booking 5005) so the nurse note flow lands on a real record.
 */

const MOCK_LATENCY_MS = 300;

/** The seeded nurse authoring a fresh note in the mock (the current session's nurse). */
const MOCK_CURRENT_NURSE_ID = 1;
const MOCK_CURRENT_NURSE_NAME = 'مریم رضایی';

let nextNoteId = 8100;

function isoDaysAgo(days: number): string {
  return new Date(Date.now() - days * 86_400_000).toISOString();
}

/** The default family-owned record seeded for a patient on first access (the customer edits from here). */
function defaultFamilyRecord(patientId: number): FamilyCareRecord {
  return {
    patientId,
    medications: [
      { id: 'm1', name: 'متفورمین ۵۰۰', dosage: '۱ قرص', frequency: 'روزی دو بار', timingNote: 'صبح و شب، بعد از غذا' },
      { id: 'm2', name: 'لوزارتان ۲۵', dosage: '۱ قرص', frequency: 'روزی یک بار', timingNote: 'صبح' },
    ],
    routine: [
      { id: 'r1', label: 'اندازه‌گیری فشار خون', timeOfDay: 'صبح', note: 'پیش از داروی فشار' },
      { id: 'r2', label: 'پیاده‌روی کوتاه', timeOfDay: 'عصر', note: null },
    ],
    tasks: [
      { id: 't1', label: 'دادن متفورمین', done: false },
      { id: 't2', label: 'اندازه‌گیری فشار خون', done: false },
      { id: 't3', label: 'پیاده‌روی کوتاه', done: false },
    ],
  };
}

/** A default continuity history seeded for a patient — two notes from DIFFERENT nurses (newest-first). */
function defaultHistory(): VisitNote[] {
  return [
    {
      id: nextNoteId++,
      bookingId: null,
      nurseProfileId: 2,
      nurseDisplayName: 'سارا محمدی',
      body: 'وضعیت بیمار پایدار بود؛ داروها طبق برنامه داده شد و فشار خون در محدودهٔ طبیعی بود.',
      taskResults: [
        { label: 'دادن متفورمین', done: true },
        { label: 'اندازه‌گیری فشار خون', done: true },
      ],
      recordedAt: isoDaysAgo(2),
    },
    {
      id: nextNoteId++,
      bookingId: null,
      nurseProfileId: MOCK_CURRENT_NURSE_ID,
      nurseDisplayName: MOCK_CURRENT_NURSE_NAME,
      body: 'پیاده‌روی کوتاه انجام شد؛ اشتها خوب بود. توصیه به ادامهٔ روتین.',
      taskResults: [{ label: 'پیاده‌روی کوتاه', done: true }],
      recordedAt: isoDaysAgo(9),
    },
  ];
}

const familyRecords = new Map<number, FamilyCareRecord>();
const histories = new Map<number, VisitNote[]>();

function ensureFamilyRecord(patientId: number): FamilyCareRecord {
  let record = familyRecords.get(patientId);
  if (!record) {
    record = defaultFamilyRecord(patientId);
    familyRecords.set(patientId, record);
  }
  return record;
}

function ensureHistory(patientId: number): VisitNote[] {
  let history = histories.get(patientId);
  if (!history) {
    history = defaultHistory();
    histories.set(patientId, history);
  }
  return history;
}

/** Deep clone so a reader can't mutate the store by reference. */
function cloneRecord(r: FamilyCareRecord): FamilyCareRecord {
  return {
    patientId: r.patientId,
    medications: r.medications.map((m) => ({ ...m })),
    routine: r.routine.map((x) => ({ ...x })),
    tasks: r.tasks.map((t) => ({ ...t })),
  };
}

function paginate<T>(all: T[], params: PageParams): Paginated<T> {
  const page = Math.max(1, params.page ?? 1);
  const pageSize = Math.max(1, params.pageSize ?? all.length);
  const start = (page - 1) * pageSize;
  return { items: all.slice(start, start + pageSize), total: all.length, page, pageSize };
}

function assertAccess(patientId: number): void {
  // The real 403 comes from the server clinical-access check; the mock denies a designated foreign patient.
  if (patientId === MOCK_FOREIGN_PATIENT_ID) {
    throw new ApiError(403, 'No clinical access to this patient', 'no_access');
  }
}

export const patientRecordsMockApi: PatientRecordsApi = {
  getFamilyRecord: async (patientId: number): Promise<FamilyCareRecord> => {
    await sleep(MOCK_LATENCY_MS);
    assertAccess(patientId);
    return cloneRecord(ensureFamilyRecord(patientId));
  },

  getRecordAccess: async (patientId: number): Promise<RecordAccess> => {
    await sleep(MOCK_LATENCY_MS);
    if (patientId === MOCK_FOREIGN_PATIENT_ID) {
      return { canView: false, canEdit: false, canAppendNote: false, deniedReason: 'no_access' };
    }
    // In the single-session mock, an authorized viewer can do everything; the SCREEN (customer vs nurse
    // shell) decides which affordances to render — the nurse view never wires the edit path (append-only).
    return { canView: true, canEdit: true, canAppendNote: true };
  },

  getPatientHistory: async (patientId: number, params: PageParams): Promise<Paginated<VisitNote>> => {
    await sleep(MOCK_LATENCY_MS);
    assertAccess(patientId);
    const history = [...ensureHistory(patientId)].sort((a, b) => Date.parse(b.recordedAt) - Date.parse(a.recordedAt));
    return paginate(
      history.map((n) => ({ ...n, taskResults: n.taskResults.map((t) => ({ ...t })) })),
      params,
    );
  },

  updateFamilyRecord: async (patientId: number, body: UpdateFamilyRecordRequest): Promise<FamilyCareRecord> => {
    await sleep(MOCK_LATENCY_MS);
    assertAccess(patientId);
    const record = ensureFamilyRecord(patientId);
    if (body.medications) record.medications = body.medications.map((m: Medication) => ({ ...m }));
    if (body.routine) record.routine = body.routine.map((r: RoutineItem) => ({ ...r }));
    if (body.tasks) record.tasks = body.tasks.map((t) => ({ ...t }));
    return cloneRecord(record);
  },

  createVisitNote: async (patientId: number, body: CreateVisitNoteRequest): Promise<WriteVisitNoteResult> => {
    await sleep(MOCK_LATENCY_MS);
    assertAccess(patientId);
    const trimmed = body.body.trim();
    if (!trimmed) throw new ApiError(400, 'Note body is required', 'empty_body');

    const id = nextNoteId++;
    const recordedAt = new Date().toISOString();
    const note: VisitNote = {
      id,
      bookingId: body.bookingId ?? null,
      nurseProfileId: MOCK_CURRENT_NURSE_ID,
      nurseDisplayName: MOCK_CURRENT_NURSE_NAME,
      body: trimmed,
      taskResults: (body.taskResults ?? []).map((t) => ({ ...t })),
      recordedAt,
    };
    ensureHistory(patientId).unshift(note);
    return { id, patientId, recordedAt };
  },
};
