import { Prisma } from '@prisma/client';
import type { Request, Response } from 'express';
import prisma from '../config/db';
import {
  DuplicatePatientPortalAccessEmailError,
  PatientPortalAccessPasswordRequiredError,
  findFallbackPatientPortalAccessByPatientId,
  sanitizeFallbackPatientPortalAccess,
  upsertFallbackPatientPortalAccess,
} from '../services/patient-account.store';
import { getFallbackPatientById } from '../services/patient.store';
import { hashPassword } from '../services/password-hash.service';

const resolveRouteParam = (value: string | string[] | undefined) => (Array.isArray(value) ? value[0] : value);

const getPatientIdFromRequest = (req: Request, res: Response) => {
  const patientId = resolveRouteParam(req.params.id);

  if (!patientId) {
    res.status(400).json({ error: 'Paciente invalido.' });
    return null;
  }

  return patientId;
};

const normalizeEmail = (value: unknown) => (typeof value === 'string' ? value.trim().toLowerCase() : '');
const normalizePassword = (value: unknown) => (typeof value === 'string' ? value.trim() : '');

const serializeDatabasePatientPortalAccess = (account: {
  id: string;
  patientId: string;
  email: string;
  active: boolean;
  lastLoginAt: Date | null;
  createdAt: Date;
  updatedAt: Date;
}) => ({
  id: account.id,
  patientId: account.patientId,
  email: account.email,
  active: account.active,
  lastLoginAt: account.lastLoginAt ? account.lastLoginAt.toISOString() : null,
  createdAt: account.createdAt.toISOString(),
  updatedAt: account.updatedAt.toISOString(),
});

export const getPatientPortalAccess = async (req: Request, res: Response) => {
  const patientId = getPatientIdFromRequest(req, res);

  if (!patientId) {
    return;
  }

  try {
    const access = await prisma.patientPortalAccess.findUnique({
      where: { patientId },
    });

    res.setHeader('X-Data-Source', 'database');
    return res.json({
      access: access ? serializeDatabasePatientPortalAccess(access) : null,
    });
  } catch (error) {
    console.warn('Database unavailable while loading patient portal access, using local fallback store.', error);
  }

  const fallbackAccess = await findFallbackPatientPortalAccessByPatientId(patientId);
  res.setHeader('X-Data-Source', 'local-fallback');
  return res.json({
    access: fallbackAccess ? sanitizeFallbackPatientPortalAccess(fallbackAccess) : null,
  });
};

export const upsertPatientPortalAccess = async (req: Request, res: Response) => {
  const patientId = getPatientIdFromRequest(req, res);

  if (!patientId) {
    return;
  }

  const email = normalizeEmail(req.body.email);
  const password = normalizePassword(req.body.password);
  const active = typeof req.body.active === 'boolean' ? req.body.active : undefined;

  if (!email) {
    return res.status(400).json({ error: 'El correo de acceso del paciente es obligatorio.' });
  }

  if (password && password.length < 6) {
    return res.status(400).json({ error: 'La contrasena debe tener al menos 6 caracteres.' });
  }

  try {
    const patient = await prisma.paciente.findUnique({
      where: { id: patientId },
    });

    if (!patient) {
      return res.status(404).json({ error: 'Paciente no encontrado.' });
    }

    const existingAccess = await prisma.patientPortalAccess.findUnique({
      where: { patientId },
    });

    if (!existingAccess && !password) {
      return res.status(400).json({ error: 'Debes definir una contrasena para crear el acceso del paciente.' });
    }

    const nextPasswordHash = password ? hashPassword(password) : undefined;
    const nextAccess = existingAccess
      ? await prisma.patientPortalAccess.update({
          where: { id: existingAccess.id },
          data: {
            email,
            ...(active !== undefined ? { active } : {}),
            ...(nextPasswordHash ? { passwordHash: nextPasswordHash } : {}),
          },
        })
      : await prisma.patientPortalAccess.create({
          data: {
            patientId,
            email,
            passwordHash: nextPasswordHash ?? '',
            active: active ?? true,
          },
        });

    res.setHeader('X-Data-Source', 'database');
    return res.json({
      access: serializeDatabasePatientPortalAccess(nextAccess),
    });
  } catch (error) {
    if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
      return res.status(409).json({ error: 'Ya existe un acceso de paciente con ese correo.' });
    }

    console.warn('Database unavailable while saving patient portal access, using local fallback store.', error);
  }

  const fallbackPatient = await getFallbackPatientById(patientId);

  if (!fallbackPatient) {
    return res.status(404).json({ error: 'Paciente no encontrado.' });
  }

  try {
    const access = await upsertFallbackPatientPortalAccess({
      patientId,
      email,
      ...(active !== undefined ? { active } : {}),
      ...(password ? { passwordHash: hashPassword(password) } : {}),
    });

    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({
      access: sanitizeFallbackPatientPortalAccess(access),
    });
  } catch (error) {
    if (error instanceof DuplicatePatientPortalAccessEmailError) {
      return res.status(409).json({ error: error.message });
    }

    if (error instanceof PatientPortalAccessPasswordRequiredError) {
      return res.status(400).json({ error: error.message });
    }

    return res.status(500).json({ error: 'Error interno del servidor.' });
  }
};
