import type { Request, Response } from 'express';
import { Prisma, Role } from '@prisma/client';
import prisma from '../config/db';
import {
  getAppointmentCommunicationSettings,
  listCommunicationLogs,
  updateAppointmentCommunicationSettings,
} from '../services/communication.store';
import {
  cancelPasswordRecoveryRequestsForUser,
  listActivePasswordRecoveryRequests,
  serializePasswordRecoveryRequest,
} from '../services/password-recovery.store';
import {
  DuplicateUserEmailError,
  type ClinicSettings,
  createFallbackUser,
  findFallbackUserById,
  getFallbackClinicSettings,
  listFallbackUsers,
  sanitizeFallbackUser,
  updateFallbackClinicSettings,
  updateFallbackUserPassword,
} from '../services/settings.store';

type SerializableUser = {
  id: string;
  email: string;
  name: string;
  role: Role;
  active: boolean;
  createdAt: Date | string;
  updatedAt: Date | string;
};

const allowedRoles = Object.values(Role);

const serializeUser = (user: SerializableUser) => ({
  id: user.id,
  email: user.email,
  name: user.name,
  role: user.role,
  active: user.active,
  createdAt: user.createdAt instanceof Date ? user.createdAt.toISOString() : user.createdAt,
  updatedAt: user.updatedAt instanceof Date ? user.updatedAt.toISOString() : user.updatedAt,
});

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

const getUserIdFromRequest = (req: Request, res: Response) => {
  const userId = resolveRouteParam(req.params.userId);

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

  return userId;
};

const normalizePassword = (value: unknown) => (typeof value === 'string' ? value.trim() : '');
const normalizeStringField = (value: unknown) => (typeof value === 'string' ? value.trim() : undefined);
const clinicEditableFields = [
  'clinicName',
  'brandShortName',
  'slogan',
  'fiscalId',
  'location',
  'address',
  'contactEmail',
  'contactPhone',
  'websiteUrl',
  'whatsappPhone',
  'currency',
  'documentFooterNote',
  'defaultDocumentIssuer',
  'installSupportLabel',
  'installSupportValue',
  'androidApkUrl',
  'iosInstallUrl',
] as const satisfies ReadonlyArray<keyof ClinicSettings>;

const serializePublicClinicSettings = (clinic: ClinicSettings) => ({
  clinicName: clinic.clinicName,
  brandShortName: clinic.brandShortName,
  slogan: clinic.slogan,
  location: clinic.location,
  address: clinic.address,
  contactEmail: clinic.contactEmail,
  contactPhone: clinic.contactPhone,
  websiteUrl: clinic.websiteUrl,
  whatsappPhone: clinic.whatsappPhone,
  documentFooterNote: clinic.documentFooterNote,
  defaultDocumentIssuer: clinic.defaultDocumentIssuer,
  installSupportLabel: clinic.installSupportLabel,
  installSupportValue: clinic.installSupportValue,
  androidApkUrl: clinic.androidApkUrl,
  iosInstallUrl: clinic.iosInstallUrl,
  updatedAt: clinic.updatedAt,
});

const findUserForPasswordActions = async (userId: string) => {
  try {
    const databaseUser = await prisma.user.findUnique({
      where: { id: userId },
    });

    if (databaseUser) {
      return {
        user: databaseUser,
        sourceMode: 'database' as const,
      };
    }
  } catch (error) {
    console.warn('Database unavailable for user password actions, using local fallback store.', error);
  }

  const fallbackUser = await findFallbackUserById(userId);

  if (fallbackUser) {
    return {
      user: fallbackUser,
      sourceMode: 'local-fallback' as const,
    };
  }

  return null;
};

export const getSettings = async (_req: Request, res: Response) => {
  const clinic = await getFallbackClinicSettings();

  try {
    const users = await prisma.user.findMany({
      orderBy: { createdAt: 'asc' },
    });

    res.setHeader('X-Data-Source', 'database');
    return res.json({
      clinic,
      users: users.map(serializeUser),
    });
  } catch (error) {
    console.warn('Database unavailable for settings users, using local fallback store.', error);
    const users = await listFallbackUsers();
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({
      clinic,
      users: users.map(sanitizeFallbackUser),
    });
  }
};

export const getPublicClinicSettings = async (_req: Request, res: Response) => {
  try {
    const clinic = await getFallbackClinicSettings();
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({ clinic: serializePublicClinicSettings(clinic) });
  } catch (error) {
    console.error('Error loading public clinic settings:', error);
    return res.status(500).json({ error: 'Error interno del servidor' });
  }
};

export const getPasswordRecoveryRequests = async (_req: Request, res: Response) => {
  try {
    const requests = await listActivePasswordRecoveryRequests();
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({
      requests: requests.map(serializePasswordRecoveryRequest),
    });
  } catch (error) {
    console.error('Error listing password recovery requests:', error);
    return res.status(500).json({ error: 'Error interno del servidor' });
  }
};

export const getCommunicationSettings = async (_req: Request, res: Response) => {
  try {
    const settings = await getAppointmentCommunicationSettings();
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({ settings });
  } catch (error) {
    console.error('Error loading communication settings:', error);
    return res.status(500).json({ error: 'Error interno del servidor' });
  }
};

export const updateCommunicationSettings = async (req: Request, res: Response) => {
  try {
    const settings = await updateAppointmentCommunicationSettings(req.body ?? {});
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({ settings });
  } catch (error) {
    console.error('Error updating communication settings:', error);
    return res.status(500).json({ error: 'Error interno del servidor' });
  }
};

export const getCommunicationLogs = async (req: Request, res: Response) => {
  const limit =
    typeof req.query.limit === 'string' && Number.isFinite(Number(req.query.limit))
      ? Number(req.query.limit)
      : 20;

  try {
    const logs = await listCommunicationLogs(limit);
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({ logs });
  } catch (error) {
    console.error('Error loading communication logs:', error);
    return res.status(500).json({ error: 'Error interno del servidor' });
  }
};

export const updateClinicSettings = async (req: Request, res: Response) => {
  const nextValues: Partial<ClinicSettings> = {};

  for (const field of clinicEditableFields) {
    const normalizedValue = normalizeStringField(req.body?.[field]);

    if (normalizedValue !== undefined) {
      nextValues[field] = normalizedValue;
    }
  }

  const updatedSettings = await updateFallbackClinicSettings(nextValues);

  res.setHeader('X-Data-Source', 'local-fallback');
  return res.json({
    clinic: updatedSettings,
  });
};

export const createUser = async (req: Request, res: Response) => {
  const email = typeof req.body.email === 'string' ? req.body.email.trim().toLowerCase() : '';
  const password = normalizePassword(req.body.password);
  const name = typeof req.body.name === 'string' ? req.body.name.trim() : '';
  const role =
    typeof req.body.role === 'string' && allowedRoles.includes(req.body.role as Role)
      ? (req.body.role as Role)
      : null;

  if (!name) {
    return res.status(400).json({ error: 'El nombre del usuario es obligatorio' });
  }

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

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

  if (!role) {
    return res.status(400).json({ error: 'Selecciona un rol valido para el usuario' });
  }

  try {
    const user = await prisma.user.create({
      data: {
        email,
        password,
        name,
        role,
        active: true,
      },
    });

    res.setHeader('X-Data-Source', 'database');
    return res.status(201).json({
      user: serializeUser(user),
    });
  } catch (error) {
    if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
      return res.status(409).json({ error: 'Ya existe un usuario con ese correo' });
    }

    console.warn('Database unavailable while creating settings user, using local fallback store.', error);

    try {
      const user = await createFallbackUser({
        email,
        password,
        name,
        role,
      });

      res.setHeader('X-Data-Source', 'local-fallback');
      return res.status(201).json({
        user: sanitizeFallbackUser(user),
      });
    } catch (fallbackError) {
      if (fallbackError instanceof DuplicateUserEmailError) {
        return res.status(409).json({ error: fallbackError.message });
      }

      console.error('Error creating user:', fallbackError);
      return res.status(500).json({ error: 'Error interno del servidor' });
    }
  }
};

export const changeUserPassword = async (req: Request, res: Response) => {
  const userId = getUserIdFromRequest(req, res);

  if (!userId) {
    return;
  }

  const currentPassword = normalizePassword(req.body.currentPassword);
  const newPassword = normalizePassword(req.body.newPassword);

  if (!currentPassword || !newPassword) {
    return res.status(400).json({ error: 'La contrasena actual y la nueva son obligatorias' });
  }

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

  const resolvedUser = await findUserForPasswordActions(userId);

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

  if (!resolvedUser.user.active) {
    return res.status(400).json({ error: 'El usuario se encuentra inactivo' });
  }

  if (resolvedUser.user.password !== currentPassword) {
    return res.status(400).json({ error: 'La contrasena actual no coincide' });
  }

  try {
    if (resolvedUser.sourceMode === 'database') {
      const updatedUser = await prisma.user.update({
        where: { id: userId },
        data: { password: newPassword, active: true },
      });

      await cancelPasswordRecoveryRequestsForUser(userId);
      res.setHeader('X-Data-Source', 'database');
      return res.json({
        user: serializeUser(updatedUser),
        message: 'Contrasena actualizada correctamente',
      });
    }

    const updatedUser = await updateFallbackUserPassword(userId, newPassword);

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

    await cancelPasswordRecoveryRequestsForUser(userId);
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({
      user: sanitizeFallbackUser(updatedUser),
      message: 'Contrasena actualizada correctamente',
    });
  } catch (error) {
    console.error('Error changing user password:', error);
    return res.status(500).json({ error: 'Error interno del servidor' });
  }
};

export const resetUserPassword = async (req: Request, res: Response) => {
  const userId = getUserIdFromRequest(req, res);

  if (!userId) {
    return;
  }

  const newPassword = normalizePassword(req.body.newPassword);

  if (!newPassword) {
    return res.status(400).json({ error: 'La nueva contrasena es obligatoria' });
  }

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

  const resolvedUser = await findUserForPasswordActions(userId);

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

  try {
    if (resolvedUser.sourceMode === 'database') {
      const updatedUser = await prisma.user.update({
        where: { id: userId },
        data: { password: newPassword, active: true },
      });

      await cancelPasswordRecoveryRequestsForUser(userId);
      res.setHeader('X-Data-Source', 'database');
      return res.json({
        user: serializeUser(updatedUser),
        message: 'Contrasena restablecida correctamente',
      });
    }

    const updatedUser = await updateFallbackUserPassword(userId, newPassword);

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

    await cancelPasswordRecoveryRequestsForUser(userId);
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({
      user: sanitizeFallbackUser(updatedUser),
      message: 'Contrasena restablecida correctamente',
    });
  } catch (error) {
    console.error('Error resetting user password:', error);
    return res.status(500).json({ error: 'Error interno del servidor' });
  }
};
