import { randomUUID } from 'crypto';
import { readDataFile, writeDataFile } from './local-store';

export type StoredPatientPortalAccess = {
  id: string;
  patientId: string;
  email: string;
  passwordHash: string;
  active: boolean;
  lastLoginAt: string | null;
  createdAt: string;
  updatedAt: string;
};

type UpsertFallbackPatientPortalAccessInput = {
  patientId: string;
  email: string;
  passwordHash?: string;
  active?: boolean;
};

export class DuplicatePatientPortalAccessEmailError extends Error {
  constructor() {
    super('Ya existe un acceso de paciente con ese correo.');
  }
}

export class PatientPortalAccessPasswordRequiredError extends Error {
  constructor() {
    super('Debes definir una contrasena para crear el acceso del paciente.');
  }
}

const fileName = 'patient-portal-access.json';

const normalizeEmail = (email: string) => email.trim().toLowerCase();

const defaultPatientPortalAccessAccounts = () => [] as StoredPatientPortalAccess[];

const normalizeStoredPatientPortalAccess = (
  account: Partial<StoredPatientPortalAccess>,
): StoredPatientPortalAccess => ({
  id: account.id ?? randomUUID(),
  patientId: account.patientId ?? '',
  email: typeof account.email === 'string' ? normalizeEmail(account.email) : '',
  passwordHash: account.passwordHash ?? '',
  active: account.active ?? true,
  lastLoginAt: typeof account.lastLoginAt === 'string' ? account.lastLoginAt : null,
  createdAt: account.createdAt ?? new Date().toISOString(),
  updatedAt: account.updatedAt ?? new Date().toISOString(),
});

const readPatientPortalAccessAccounts = async () => {
  const accounts = await readDataFile<Array<Partial<StoredPatientPortalAccess>>>(
    fileName,
    defaultPatientPortalAccessAccounts,
  );

  return accounts.map(normalizeStoredPatientPortalAccess);
};

const writePatientPortalAccessAccounts = async (accounts: StoredPatientPortalAccess[]) => {
  await writeDataFile(fileName, accounts);
};

export const sanitizeFallbackPatientPortalAccess = (account: StoredPatientPortalAccess) => ({
  id: account.id,
  patientId: account.patientId,
  email: account.email,
  active: account.active,
  lastLoginAt: account.lastLoginAt,
  createdAt: account.createdAt,
  updatedAt: account.updatedAt,
});

export const findFallbackPatientPortalAccessById = async (accountId: string) => {
  const accounts = await readPatientPortalAccessAccounts();
  return accounts.find((account) => account.id === accountId) ?? null;
};

export const findFallbackPatientPortalAccessByPatientId = async (patientId: string) => {
  const accounts = await readPatientPortalAccessAccounts();
  return accounts.find((account) => account.patientId === patientId) ?? null;
};

export const findFallbackPatientPortalAccessByEmail = async (email: string) => {
  const normalizedEmail = normalizeEmail(email);
  const accounts = await readPatientPortalAccessAccounts();
  return accounts.find((account) => account.email === normalizedEmail) ?? null;
};

export const upsertFallbackPatientPortalAccess = async (
  input: UpsertFallbackPatientPortalAccessInput,
) => {
  const accounts = await readPatientPortalAccessAccounts();
  const normalizedEmail = normalizeEmail(input.email);
  const existingAccount = accounts.find((account) => account.patientId === input.patientId) ?? null;
  const emailOwner = accounts.find((account) => account.email === normalizedEmail) ?? null;

  if (emailOwner && emailOwner.id !== existingAccount?.id) {
    throw new DuplicatePatientPortalAccessEmailError();
  }

  if (!existingAccount && !input.passwordHash) {
    throw new PatientPortalAccessPasswordRequiredError();
  }

  const now = new Date().toISOString();

  if (existingAccount) {
    const updatedAccount: StoredPatientPortalAccess = {
      id: existingAccount.id,
      patientId: existingAccount.patientId,
      email: normalizedEmail,
      passwordHash: input.passwordHash ?? existingAccount.passwordHash,
      active: input.active ?? existingAccount.active,
      lastLoginAt: existingAccount.lastLoginAt,
      createdAt: existingAccount.createdAt,
      updatedAt: now,
    };

    const updatedAccounts = accounts.map((account) => (account.id === existingAccount.id ? updatedAccount : account));
    await writePatientPortalAccessAccounts(updatedAccounts);
    return updatedAccount;
  }

  const createdAccount: StoredPatientPortalAccess = {
    id: randomUUID(),
    patientId: input.patientId,
    email: normalizedEmail,
    passwordHash: input.passwordHash ?? '',
    active: input.active ?? true,
    lastLoginAt: null,
    createdAt: now,
    updatedAt: now,
  };

  accounts.unshift(createdAccount);
  await writePatientPortalAccessAccounts(accounts);
  return createdAccount;
};

export const updateFallbackPatientPortalAccessLastLogin = async (accountId: string, loginAt: string) => {
  const accounts = await readPatientPortalAccessAccounts();
  const accountIndex = accounts.findIndex((account) => account.id === accountId);

  if (accountIndex === -1) {
    return null;
  }

  const currentAccount = accounts[accountIndex]!;
  const updatedAccount: StoredPatientPortalAccess = {
    ...currentAccount,
    lastLoginAt: loginAt,
    updatedAt: loginAt,
  };

  accounts[accountIndex] = updatedAccount;
  await writePatientPortalAccessAccounts(accounts);
  return updatedAccount;
};
