import { Router } from 'express';
import bcrypt from 'bcryptjs';
import { Prisma } from '@prisma/client';
import { z } from 'zod';
import { prisma } from '../db';
import { badRequest, conflict, notFound } from '../lib/errors';
import { nextEmployeeCode } from '../lib/ids';
import { param } from '../lib/params';
import { SYSTEM_ROLES, systemRoleToDb } from '../lib/roles';
import { AuthedRequest, requireAuth, requireSuperAdmin } from '../middleware/auth';
import { serializeEmployee } from '../serializers/employee';

export const employeesRouter = Router();

employeesRouter.use(requireAuth, requireSuperAdmin);

const roleSchema = z.enum(SYSTEM_ROLES);

const phoneSchema = z
  .string()
  .trim()
  .optional()
  .nullable()
  .refine((v) => {
    if (v == null || v === '') return true;
    return /^\+?\d{1,10}$/.test(v);
  }, 'Phone: use + and digits only (max 10 digits)');

const basicLabelSchema = z
  .string()
  .trim()
  .optional()
  .nullable()
  .refine((v) => {
    if (v == null || v === '') return true;
    return /^[A-Za-z0-9\s,.\(\)\/\-]+$/.test(v);
  }, 'Only letters, numbers, and , . ( ) / - are allowed');

const createSchema = z.object({
  name: z.string().trim().min(1),
  email: z.string().email(),
  phone: phoneSchema,
  department: basicLabelSchema,
  designation: basicLabelSchema,
  role: roleSchema,
  active: z.boolean().optional().default(true),
  avatarColor: z.string().optional(),
  password: z.string().min(8).optional(),
});

const updateSchema = createSchema.partial().extend({
  password: z.string().min(8).optional(),
});

employeesRouter.get('/', async (req, res, next) => {
  try {
    const q = String(req.query.q || '').trim().toLowerCase();
    const role = req.query.role ? String(req.query.role) : '';
    const active =
      req.query.active === undefined
        ? undefined
        : req.query.active === 'true' || req.query.active === '1';

    const where: Prisma.EmployeeWhereInput = {};
    if (active !== undefined) where.active = active;
    if (role) where.role = systemRoleToDb(role);
    if (q) {
      where.OR = [
        { name: { contains: q, mode: 'insensitive' } },
        { email: { contains: q, mode: 'insensitive' } },
        { code: { contains: q, mode: 'insensitive' } },
        { department: { contains: q, mode: 'insensitive' } },
        { designation: { contains: q, mode: 'insensitive' } },
      ];
    }

    const rows = await prisma.employee.findMany({
      where,
      orderBy: [{ createdAt: 'desc' }, { name: 'asc' }],
    });
    res.json({ employees: rows.map(serializeEmployee) });
  } catch (err) {
    next(err);
  }
});

employeesRouter.get('/:id', async (req, res, next) => {
  try {
    const id = param(req.params.id);
    const emp = await prisma.employee.findFirst({
      where: {
        OR: [{ id }, { code: id }],
      },
    });
    if (!emp) throw notFound('Employee not found');
    res.json({ employee: serializeEmployee(emp) });
  } catch (err) {
    next(err);
  }
});

employeesRouter.post('/', async (req, res, next) => {
  try {
    const body = createSchema.parse(req.body);
    const email = body.email.toLowerCase();
    const exists = await prisma.employee.findUnique({ where: { email } });
    if (exists) throw conflict('Email already in use');

    const code = await nextEmployeeCode();
    const password = body.password || 'Password123!';
    const passwordHash = await bcrypt.hash(password, 10);

    const emp = await prisma.employee.create({
      data: {
        code,
        name: body.name,
        email,
        phone: body.phone || null,
        department: body.department || null,
        designation: body.designation || null,
        role: systemRoleToDb(body.role),
        active: body.active ?? true,
        avatarColor: body.avatarColor || '#3C8DBC',
        passwordHash,
      },
    });

    res.status(201).json({
      employee: serializeEmployee(emp),
      temporaryPassword: body.password ? undefined : password,
    });
  } catch (err) {
    next(err);
  }
});

employeesRouter.patch('/:id', async (req, res, next) => {
  try {
    const id = param(req.params.id);
    const emp = await prisma.employee.findFirst({
      where: { OR: [{ id }, { code: id }] },
    });
    if (!emp) throw notFound('Employee not found');

    const body = updateSchema.parse(req.body);
    if (body.email) {
      const email = body.email.toLowerCase();
      const clash = await prisma.employee.findFirst({
        where: { email, NOT: { id: emp.id } },
      });
      if (clash) throw conflict('Email already in use');
    }

    const data: Prisma.EmployeeUpdateInput = {};
    if (body.name !== undefined) data.name = body.name;
    if (body.email !== undefined) data.email = body.email.toLowerCase();
    if (body.phone !== undefined) data.phone = body.phone;
    if (body.department !== undefined) data.department = body.department;
    if (body.designation !== undefined) data.designation = body.designation;
    if (body.role !== undefined) data.role = systemRoleToDb(body.role);
    if (body.active !== undefined) data.active = body.active;
    if (body.avatarColor !== undefined) data.avatarColor = body.avatarColor;
    if (body.password) data.passwordHash = await bcrypt.hash(body.password, 10);

    const updated = await prisma.employee.update({ where: { id: emp.id }, data });
    res.json({ employee: serializeEmployee(updated) });
  } catch (err) {
    next(err);
  }
});

employeesRouter.post('/:id/deactivate', async (req: AuthedRequest, res, next) => {
  try {
    const id = param(req.params.id);
    const emp = await prisma.employee.findFirst({
      where: { OR: [{ id }, { code: id }] },
    });
    if (!emp) throw notFound('Employee not found');
    if (emp.id === req.user?.id) throw badRequest('You cannot deactivate your own account');

    const openTasks = await prisma.task.count({
      where: {
        OR: [
          { assigneeId: emp.id },
          { assignments: { some: { employeeId: emp.id } } },
        ],
        stage: { not: 'Done' },
      },
    });
    const openBugs = await prisma.bug.count({
      where: {
        assigneeId: emp.id,
        status: { not: 'Closed' },
      },
    });

    const updated = await prisma.employee.update({
      where: { id: emp.id },
      data: { active: false },
    });

    res.json({
      employee: serializeEmployee(updated),
      warning:
        openTasks || openBugs
          ? `Employee is assigned to ${openTasks} open task(s) and ${openBugs} open bug(s). History is retained.`
          : null,
    });
  } catch (err) {
    next(err);
  }
});

employeesRouter.post('/:id/activate', async (req, res, next) => {
  try {
    const id = param(req.params.id);
    const emp = await prisma.employee.findFirst({
      where: { OR: [{ id }, { code: id }] },
    });
    if (!emp) throw notFound('Employee not found');
    const updated = await prisma.employee.update({
      where: { id: emp.id },
      data: { active: true },
    });
    res.json({ employee: serializeEmployee(updated) });
  } catch (err) {
    next(err);
  }
});
