import type { Request, Response } from 'express';
import prisma from '../config/db';
import { getPatientAuthContext } from '../middleware/patient-auth.middleware';
import {
  findFallbackPatientPortalAccessByEmail,
  findFallbackPatientPortalAccessById,
  sanitizeFallbackPatientPortalAccess,
  updateFallbackPatientPortalAccessLastLogin,
} from '../services/patient-account.store';
import { getFallbackPatientById } from '../services/patient.store';
import { verifyPassword } from '../services/password-hash.service';
import { createPatientSessionToken } from '../services/patient-session.service';

type SerializablePatient = {
  id: string;
  dni: string;
  nombres: string;
  apellidos: string;
  fechaNacimiento?: Date | string | null;
  email: string | null;
  telefono: string | null;
  direccion?: string | null;
  fechaRegistro: Date | string;
  updatedAt: Date | string;
};

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

const serializePatientIdentity = (patient: SerializablePatient) => ({
  id: patient.id,
  dni: patient.dni,
  nombres: patient.nombres,
  apellidos: patient.apellidos,
  nombreCompleto: `${patient.nombres} ${patient.apellidos}`.trim(),
  fechaNacimiento:
    patient.fechaNacimiento instanceof Date
      ? patient.fechaNacimiento.toISOString()
      : patient.fechaNacimiento ?? null,
  email: patient.email,
  telefono: patient.telefono,
  direccion: patient.direccion ?? null,
  fechaRegistro:
    patient.fechaRegistro instanceof Date ? patient.fechaRegistro.toISOString() : patient.fechaRegistro,
  updatedAt: patient.updatedAt instanceof Date ? patient.updatedAt.toISOString() : patient.updatedAt,
});

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 loginPatient = async (req: Request, res: Response) => {
  const email = normalizeEmail(req.body.email);
  const password = normalizePassword(req.body.password);

  if (!email || !password) {
    return res.status(400).json({ error: 'Correo y contrasena son obligatorios.' });
  }

  try {
    const account = await prisma.patientPortalAccess.findUnique({
      where: { email },
      include: { patient: true },
    });

    if (!account || !account.active || !verifyPassword(password, account.passwordHash)) {
      return res.status(401).json({ error: 'Credenciales invalidas.' });
    }

    const loginAt = new Date();
    await prisma.patientPortalAccess.update({
      where: { id: account.id },
      data: { lastLoginAt: loginAt },
    });

    const session = createPatientSessionToken({
      accountId: account.id,
      patientId: account.patientId,
      email: account.email,
    });

    res.setHeader('X-Data-Source', 'database');
    return res.json({
      token: session.token,
      expiresAt: session.expiresAt,
      access: serializeDatabasePatientPortalAccess({
        ...account,
        lastLoginAt: loginAt,
      }),
      patient: serializePatientIdentity(account.patient),
    });
  } catch (error) {
    console.warn('Database unavailable for patient auth, using local fallback store.', error);
  }

  const fallbackAccount = await findFallbackPatientPortalAccessByEmail(email);

  if (!fallbackAccount || !fallbackAccount.active || !verifyPassword(password, fallbackAccount.passwordHash)) {
    return res.status(401).json({ error: 'Credenciales invalidas.' });
  }

  const fallbackPatient = await getFallbackPatientById(fallbackAccount.patientId);

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

  const loginAt = new Date().toISOString();
  const updatedAccount = await updateFallbackPatientPortalAccessLastLogin(fallbackAccount.id, loginAt);
  const session = createPatientSessionToken({
    accountId: fallbackAccount.id,
    patientId: fallbackAccount.patientId,
    email: fallbackAccount.email,
  });

  res.setHeader('X-Data-Source', 'local-fallback');
  return res.json({
    token: session.token,
    expiresAt: session.expiresAt,
    access: sanitizeFallbackPatientPortalAccess(updatedAccount ?? fallbackAccount),
    patient: serializePatientIdentity(fallbackPatient),
  });
};

export const getCurrentPatientSession = async (req: Request, res: Response) => {
  const patientAuth = getPatientAuthContext(req);

  if (!patientAuth) {
    return res.status(401).json({ error: 'Debes iniciar sesion como paciente.' });
  }

  if (patientAuth.sourceMode === 'database') {
    const account = await prisma.patientPortalAccess.findUnique({
      where: { id: patientAuth.accountId },
      include: { patient: true },
    });

    if (!account || !account.active) {
      return res.status(401).json({ error: 'La sesion del paciente ya no es valida.' });
    }

    res.setHeader('X-Data-Source', 'database');
    return res.json({
      access: serializeDatabasePatientPortalAccess(account),
      patient: serializePatientIdentity(account.patient),
    });
  }

  const fallbackAccount = await findFallbackPatientPortalAccessById(patientAuth.accountId);
  const fallbackPatient = await getFallbackPatientById(patientAuth.patientId);

  if (!fallbackAccount || !fallbackPatient || !fallbackAccount.active) {
    return res.status(401).json({ error: 'La sesion del paciente ya no es valida.' });
  }

  res.setHeader('X-Data-Source', 'local-fallback');
  return res.json({
    access: sanitizeFallbackPatientPortalAccess(fallbackAccount),
    patient: serializePatientIdentity(fallbackPatient),
  });
};
