/**
 * server.js
 * Backend — Sistema de Control de Asistencia
 * Stack: Express · Prisma ORM · PostgreSQL · bcrypt · JWT · Nodemailer
 */

require('dotenv').config();
const express    = require('express');
const cors       = require('cors');
const bcrypt     = require('bcrypt');
const jwt        = require('jsonwebtoken');
const crypto     = require('crypto');
const nodemailer = require('nodemailer');
const { PrismaClient } = require('@prisma/client');

const app    = express();
const prisma = new PrismaClient();
const PORT   = process.env.PORT || 3000;

app.use(cors());
app.use(express.json());

// ─────────────────────────────────────────────
//  HELPERS
// ─────────────────────────────────────────────

/** Sign a JWT for a user */
function signToken(user) {
  return jwt.sign(
    { id: user.id, email: user.email, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: process.env.JWT_EXPIRES_IN || '8h' }
  );
}

/** Express middleware — validates Bearer JWT */
function requireAuth(req, res, next) {
  const header = req.headers.authorization;
  if (!header?.startsWith('Bearer ')) {
    return res.status(401).json({ ok: false, message: 'Token requerido.' });
  }
  try {
    req.user = jwt.verify(header.slice(7), process.env.JWT_SECRET);
    next();
  } catch {
    return res.status(401).json({ ok: false, message: 'Token inválido o expirado.' });
  }
}

/** Nodemailer transporter (configured via .env) */
function createTransporter() {
  return nodemailer.createTransport({
    host:   process.env.SMTP_HOST,
    port:   Number(process.env.SMTP_PORT) || 587,
    secure: process.env.SMTP_SECURE === 'true',
    auth: {
      user: process.env.SMTP_USER,
      pass: process.env.SMTP_PASS,
    },
  });
}

/** Send the password-reset email */
async function sendResetEmail(toEmail, userName, resetUrl) {
  const transporter = createTransporter();
  await transporter.sendMail({
    from:    `"Control de Asistencia" <${process.env.SMTP_FROM || process.env.SMTP_USER}>`,
    to:      toEmail,
    subject: 'Recuperación de contraseña — Control de Asistencia',
    html: `
      <div style="font-family:Arial,sans-serif;max-width:520px;margin:auto;padding:32px;background:#f9fafb;border-radius:8px">
        <h2 style="color:#4f46e5;margin-bottom:8px">Hola, ${userName}</h2>
        <p style="color:#374151">Recibimos una solicitud para restablecer la contraseña de tu cuenta.</p>
        <p style="color:#374151">Haz clic en el siguiente botón para continuar. El enlace expira en <strong>1 hora</strong>.</p>
        <a href="${resetUrl}"
           style="display:inline-block;margin:24px 0;padding:12px 28px;background:#4f46e5;color:#fff;border-radius:6px;text-decoration:none;font-weight:bold">
          Restablecer contraseña
        </a>
        <p style="color:#9ca3af;font-size:12px">Si no solicitaste esto, ignora este correo. Tu contraseña no cambiará.</p>
        <hr style="border:none;border-top:1px solid #e5e7eb;margin:24px 0">
        <p style="color:#9ca3af;font-size:11px">© ${new Date().getFullYear()} Sistema de Control de Asistencia</p>
      </div>`,
  });
}

// ─────────────────────────────────────────────
//  AUTH ROUTES
// ─────────────────────────────────────────────

/**
 * POST /api/auth/login
 * Body: { email, password }
 */
app.post('/api/auth/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    if (!email || !password)
      return res.status(400).json({ ok: false, message: 'Email y contraseña requeridos.' });

    const user = await prisma.user.findUnique({ where: { email } });
    if (!user || !user.active)
      return res.status(401).json({ ok: false, message: 'Credenciales incorrectas.' });

    const valid = await bcrypt.compare(password, user.password);
    if (!valid)
      return res.status(401).json({ ok: false, message: 'Credenciales incorrectas.' });

    const token = signToken(user);
    const { password: _, ...safeUser } = user;
    res.json({ ok: true, token, user: safeUser });
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

/**
 * POST /api/auth/forgot-password
 * Body: { email }
 * Generates a reset token and sends an email to the user.
 */
app.post('/api/auth/forgot-password', async (req, res) => {
  try {
    const { email } = req.body;
    if (!email)
      return res.status(400).json({ ok: false, message: 'Email requerido.' });

    // Always return 200 to avoid email enumeration attacks
    const user = await prisma.user.findUnique({ where: { email } });
    if (!user || !user.active) {
      return res.json({ ok: true, message: 'Si el correo existe recibirás un enlace en breve.' });
    }

    // Expire any previous unused tokens for this user
    await prisma.passwordResetToken.updateMany({
      where: { userId: user.id, used: false },
      data:  { used: true },
    });

    // Create a new token
    const rawToken = crypto.randomBytes(32).toString('hex');
    const expiresAt = new Date(Date.now() + 60 * 60 * 1000); // 1 hour from now

    await prisma.passwordResetToken.create({
      data: { token: rawToken, userId: user.id, expiresAt },
    });

    const resetUrl = `${process.env.FRONTEND_URL || 'http://localhost:5500'}/reset-password.html?token=${rawToken}`;

    await sendResetEmail(user.email, user.name, resetUrl);
    console.log(`📧 Reset email sent to ${user.email}`);

    res.json({ ok: true, message: 'Si el correo existe recibirás un enlace en breve.' });
  } catch (err) {
    console.error('forgot-password error:', err);
    res.status(500).json({ ok: false, message: 'No se pudo enviar el correo. Intente más tarde.' });
  }
});

/**
 * POST /api/auth/reset-password
 * Body: { token, newPassword }
 */
app.post('/api/auth/reset-password', async (req, res) => {
  try {
    const { token, newPassword } = req.body;
    if (!token || !newPassword)
      return res.status(400).json({ ok: false, message: 'Token y nueva contraseña requeridos.' });

    if (newPassword.length < 6)
      return res.status(400).json({ ok: false, message: 'La contraseña debe tener al menos 6 caracteres.' });

    const record = await prisma.passwordResetToken.findUnique({
      where: { token },
      include: { user: true },
    });

    if (!record || record.used || record.expiresAt < new Date())
      return res.status(400).json({ ok: false, message: 'El enlace es inválido o ha expirado.' });

    const hashed = await bcrypt.hash(newPassword, 10);

    await prisma.$transaction([
      prisma.user.update({
        where: { id: record.userId },
        data:  { password: hashed },
      }),
      prisma.passwordResetToken.update({
        where: { id: record.id },
        data:  { used: true },
      }),
    ]);

    res.json({ ok: true, message: 'Contraseña actualizada exitosamente.' });
  } catch (err) {
    console.error('reset-password error:', err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

/**
 * GET /api/auth/me  (protected)
 */
app.get('/api/auth/me', requireAuth, async (req, res) => {
  const user = await prisma.user.findUnique({
    where:  { id: req.user.id },
    select: { id: true, email: true, name: true, role: true, active: true },
  });
  if (!user) return res.status(404).json({ ok: false });
  res.json({ ok: true, user });
});

// ─────────────────────────────────────────────
//  COMPANIES
// ─────────────────────────────────────────────

/** GET /api/companies  ?status=active|inactive|all  &q=search */
app.get('/api/companies', requireAuth, async (req, res) => {
  try {
    const { status = 'all', q = '' } = req.query;
    const where = {};
    if (status !== 'all') where.status = status;
    if (q) where.OR = [
      { name: { contains: q, mode: 'insensitive' } },
      { rut:  { contains: q, mode: 'insensitive' } },
    ];

    const companies = await prisma.company.findMany({
      where,
      orderBy: { name: 'asc' },
    });
    res.json(companies);
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

/** GET /api/companies/:id  — includes locales and employee count */
app.get('/api/companies/:id', requireAuth, async (req, res) => {
  try {
    const company = await prisma.company.findUnique({
      where: { id: Number(req.params.id) },
      include: {
        locales:   { orderBy: { name: 'asc' } },
        _count:    { select: { employees: true } },
      },
    });
    if (!company) return res.status(404).json({ ok: false, message: 'Empresa no encontrada.' });
    res.json(company);
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

// ─────────────────────────────────────────────
//  EMPLOYEES
// ─────────────────────────────────────────────

/**
 * GET /api/employees
 * Query: companyId, region, jornada, turno, local, cargo, est, q
 */
app.get('/api/employees', requireAuth, async (req, res) => {
  try {
    const { companyId, region, jornada, turno, local, cargo, est, q } = req.query;
    const where = {};
    if (companyId && companyId !== 'all') where.companyId = Number(companyId);
    if (region    && region    !== 'Todas') where.region  = region;
    if (jornada   && jornada   !== 'Todas') where.jornada = jornada;
    if (turno     && turno     !== 'Todos') where.turno   = turno;
    if (local     && local     !== 'Todos') where.localId = local;
    if (cargo     && cargo     !== 'Todos') where.cargo   = cargo;
    if (est       && est       !== 'todos') where.est     = est;
    if (q) where.OR = [
      { name: { contains: q, mode: 'insensitive' } },
      { rut:  { contains: q, mode: 'insensitive' } },
    ];

    const employees = await prisma.employee.findMany({
      where,
      include: {
        company: { select: { id: true, name: true, rut: true } },
        local:   { select: { id: true, name: true, region: true, direccion: true } },
      },
      orderBy: { name: 'asc' },
    });

    // Flatten for frontend backward compatibility
    const result = employees.map(e => ({
      ...e,
      local:          e.localId,
      localName:      e.local?.name,
      localDireccion: e.local?.direccion,
    }));

    res.json(result);
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

/** GET /api/employees/:id  — full details + last 100 marcaciones */
app.get('/api/employees/:id', requireAuth, async (req, res) => {
  try {
    const employee = await prisma.employee.findUnique({
      where: { id: Number(req.params.id) },
      include: {
        company:     { select: { id: true, name: true, rut: true } },
        local:       true,
        marcaciones: { orderBy: { timestamp: 'desc' }, take: 100 },
      },
    });
    if (!employee) return res.status(404).json({ ok: false, message: 'Empleado no encontrado.' });
    res.json(employee);
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

// ─────────────────────────────────────────────
//  MARCACIONES
// ─────────────────────────────────────────────

/** GET /api/marcaciones  ?employeeId=&startDate=&endDate= */
app.get('/api/marcaciones', requireAuth, async (req, res) => {
  try {
    const { employeeId, startDate, endDate } = req.query;
    const where = {};
    if (employeeId) where.employeeId = Number(employeeId);
    if (startDate || endDate) {
      where.timestamp = {};
      if (startDate) where.timestamp.gte = new Date(startDate + 'T00:00:00');
      if (endDate)   where.timestamp.lte = new Date(endDate   + 'T23:59:59');
    }

    const marcaciones = await prisma.marcacion.findMany({
      where,
      include: {
        employee: { select: { id: true, name: true, rut: true, cargo: true } },
      },
      orderBy: { timestamp: 'desc' },
    });

    res.json(marcaciones.map(m => ({
      hash:        m.hash,
      employeeId:  m.employeeId,
      timestamp:   m.timestamp.toISOString(),
      type:        m.type,
      geolocation: { lat: m.lat, lng: m.lng },
      employee:    m.employee,
    })));
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

/** GET /api/marcaciones/validate/:hash */
app.get('/api/marcaciones/validate/:hash', requireAuth, async (req, res) => {
  try {
    const marcacion = await prisma.marcacion.findFirst({
      where: { hash: { startsWith: req.params.hash } },
      include: {
        employee: {
          select: {
            id: true, name: true, rut: true, cargo: true,
            jornada: true, turno: true, region: true, est: true,
            // Empresa
            company: {
              select: { id: true, rut: true, name: true, status: true }
            },
            // Local / Faena
            local: {
              select: { id: true, name: true, region: true, direccion: true }
            }
          }
        }
      },
    });

    if (!marcacion)
      return res.status(404).json({ ok: false, message: 'Hash no encontrado en el sistema.' });

    const emp = marcacion.employee;

    res.json({
      ok: true,
      marcacion: {
        hash:        marcacion.hash,
        employeeId:  marcacion.employeeId,
        timestamp:   marcacion.timestamp.toISOString(),
        type:        marcacion.type,
        geolocation: { lat: marcacion.lat, lng: marcacion.lng },
      },
      employee: {
        id:      emp.id,
        name:    emp.name,
        rut:     emp.rut,
        cargo:   emp.cargo,
        jornada: emp.jornada,
        turno:   emp.turno,
        region:  emp.region,
        est:     emp.est,
      },
      employer: emp.company
        ? { id: emp.company.id, rut: emp.company.rut, name: emp.company.name, status: emp.company.status }
        : null,
      local: emp.local
        ? { id: emp.local.id, name: emp.local.name, region: emp.local.region, direccion: emp.local.direccion }
        : null,
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

// ─────────────────────────────────────────────
//  INCIDENTES
// ─────────────────────────────────────────────

/** GET /api/incidentes  ?startDate=&endDate= */
app.get('/api/incidentes', requireAuth, async (req, res) => {
  try {
    const { startDate, endDate } = req.query;
    const where = {};
    if (startDate || endDate) {
      where.start = {};
      if (startDate) where.start.gte = new Date(startDate + 'T00:00:00');
      if (endDate)   where.start.lte = new Date(endDate   + 'T23:59:59');
    }

    const incidentes = await prisma.incidente.findMany({
      where,
      orderBy: { start: 'desc' },
    });

    res.json(incidentes.map(inc => ({
      start:       inc.start.toISOString(),
      end:         inc.end.toISOString(),
      description: inc.description,
    })));
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

// ─────────────────────────────────────────────
//  FILTERS (dropdown options, derived from DB)
// ─────────────────────────────────────────────

/** GET /api/filters */
app.get('/api/filters', requireAuth, async (req, res) => {
  try {
    const [jornadasRaw, turnosRaw, regionesRaw, cargosRaw, locales] = await Promise.all([
      prisma.employee.findMany({ distinct: ['jornada'], select: { jornada: true }, orderBy: { jornada: 'asc' } }),
      prisma.employee.findMany({ distinct: ['turno'],   select: { turno:   true }, orderBy: { turno:   'asc' } }),
      prisma.employee.findMany({ distinct: ['region'],  select: { region:  true }, orderBy: { region:  'asc' } }),
      prisma.employee.findMany({ distinct: ['cargo'],   select: { cargo:   true }, orderBy: { cargo:   'asc' } }),
      prisma.local.findMany({ orderBy: { name: 'asc' } }),
    ]);

    res.json({
      jornadas: ['Todas', ...jornadasRaw.map(j => j.jornada)],
      turnos:   ['Todos', ...turnosRaw.map(t   => t.turno)],
      regiones: ['Todas', ...regionesRaw.map(r => r.region)],
      cargos:   ['Todos', ...cargosRaw.map(c   => c.cargo)],
      locales: [
        { id: 'Todos', name: 'Todos los locales', region: 'Todas', direccion: 'N/A' },
        ...locales,
      ],
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

/** GET /api/locales  ?companyId=&region= */
app.get('/api/locales', requireAuth, async (req, res) => {
  try {
    const { companyId, region } = req.query;
    const where = {};
    if (companyId && companyId !== 'all') where.companyId = Number(companyId);
    if (region    && region    !== 'Todas') where.region  = region;

    const locales = await prisma.local.findMany({ where, orderBy: { name: 'asc' } });
    res.json([
      { id: 'Todos', name: 'Todos los locales', region: 'Todas', direccion: 'N/A' },
      ...locales,
    ]);
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});


// ─────────────────────────────────────────────
//  FESTIVOS
// ─────────────────────────────────────────────

/**
 * GET /api/festivos
 * Query: anio=2024  (opcional — sin filtro devuelve todos)
 * Returns array of { fecha, descripcion, anio, irrenunciable }
 */
app.get('/api/festivos', requireAuth, async (req, res) => {
  try {
    const { anio } = req.query;
    const where = anio ? { anio: Number(anio) } : {};

    const festivos = await prisma.festivo.findMany({
      where,
      orderBy: { fecha: 'asc' },
      select: { id: true, fecha: true, descripcion: true, anio: true, irrenunciable: true },
    });

    // Devuelve fechas como strings YYYY-MM-DD para fácil comparación en el frontend
    res.json(festivos.map(f => ({
      ...f,
      fecha: f.fecha.toISOString().split('T')[0],
    })));
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

/**
 * POST /api/festivos
 * Body: { fecha: 'YYYY-MM-DD', descripcion: string, irrenunciable?: boolean }
 * Agrega un festivo manualmente
 */
app.post('/api/festivos', requireAuth, async (req, res) => {
  try {
    const { fecha, descripcion, irrenunciable = false } = req.body;
    if (!fecha || !descripcion)
      return res.status(400).json({ ok: false, message: 'fecha y descripcion son requeridos.' });

    const fechaDate = new Date(fecha + 'T00:00:00');
    const anio      = fechaDate.getFullYear();

    const festivo = await prisma.festivo.upsert({
      where:  { fecha: fechaDate },
      update: { descripcion, irrenunciable },
      create: { fecha: fechaDate, descripcion, anio, irrenunciable },
    });

    res.json({ ok: true, festivo: { ...festivo, fecha: festivo.fecha.toISOString().split('T')[0] } });
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});

/**
 * DELETE /api/festivos/:id
 * Elimina un festivo por id
 */
app.delete('/api/festivos/:id', requireAuth, async (req, res) => {
  try {
    await prisma.festivo.delete({ where: { id: Number(req.params.id) } });
    res.json({ ok: true });
  } catch (err) {
    res.status(404).json({ ok: false, message: 'Festivo no encontrado.' });
  }
});

// ─────────────────────────────────────────────
//  STATS
// ─────────────────────────────────────────────

/** GET /api/stats */
app.get('/api/stats', requireAuth, async (req, res) => {
  try {
    const [totalCompanies, activeCompanies, totalEmployees, totalMarcaciones, totalIncidentes] =
      await Promise.all([
        prisma.company.count(),
        prisma.company.count({ where: { status: 'active' } }),
        prisma.employee.count(),
        prisma.marcacion.count(),
        prisma.incidente.count(),
      ]);

    res.json({
      totalCompanies,
      activeCompanies,
      inactiveCompanies: totalCompanies - activeCompanies,
      totalEmployees,
      totalMarcaciones,
      totalIncidentes,
    });
  } catch (err) {
    console.error(err);
    res.status(500).json({ ok: false, message: 'Error del servidor.' });
  }
});


// ─────────────────────────────────────────────
//  FRONTEND — servir archivos estáticos
// ─────────────────────────────────────────────
const path = require('path');

// Sirve todo lo que esté en la carpeta Frontend/
app.use(express.static(path.join(__dirname, '../Frontend')));

// Cualquier ruta que no sea /api devuelve el index.html
app.get('*', (req, res) => {
  if (!req.path.startsWith('/api')) {
    res.sendFile(path.join(__dirname, '../Frontend', 'index.html'));
  }
});

// ─────────────────────────────────────────────
//  GRACEFUL SHUTDOWN
// ─────────────────────────────────────────────
async function shutdown() {
  await prisma.$disconnect();
  process.exit(0);
}
process.on('SIGTERM', shutdown);
process.on('SIGINT',  shutdown);

// ─────────────────────────────────────────────
//  START
// ─────────────────────────────────────────────
app.listen(PORT, () => {
  console.log(`\n🚀 Backend running → http://localhost:${PORT}`);
  console.log('\n  Auth:');
  console.log('    POST /api/auth/login');
  console.log('    POST /api/auth/forgot-password');
  console.log('    POST /api/auth/reset-password');
  console.log('    GET  /api/auth/me');
  console.log('\n  Data (JWT required):');
  console.log('    GET  /api/companies[/:id]');
  console.log('    GET  /api/employees[/:id]');
  console.log('    GET  /api/marcaciones');
  console.log('    GET  /api/marcaciones/validate/:hash');
  console.log('    GET  /api/incidentes');
  console.log('    GET  /api/filters');
  console.log('    GET  /api/locales');
  console.log('    GET  /api/stats\n');
});
