import { Router } from 'express';
import { Prisma } from '@prisma/client';
import { z } from 'zod';
import { prisma } from '../db';
import { badRequest, conflict, notFound } from '../lib/errors';
import { nextClientCode } from '../lib/ids';
import { param } from '../lib/params';
import { AuthedRequest, requireAuth, requireSuperAdmin } from '../middleware/auth';

export const clientsRouter = Router();

clientsRouter.use(requireAuth);

const optionalEmail = z
  .string()
  .trim()
  .optional()
  .nullable()
  .refine((v) => !v || z.string().email().safeParse(v).success, 'Enter a valid email');

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 basicTextSchema = 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 writeSchema = z.object({
  name: z.string().trim().min(1),
  contactName: basicTextSchema,
  email: optionalEmail,
  phone: phoneSchema,
  address: basicTextSchema,
  active: z.boolean().optional(),
});

const updateSchema = writeSchema.partial();

export function serializeClient(c: {
  id: string;
  code: string;
  name: string;
  contactName: string | null;
  email: string | null;
  phone: string | null;
  address: string;
  active: boolean;
}) {
  return {
    id: c.id,
    code: c.code,
    name: c.name,
    contactName: c.contactName || '',
    email: c.email || '',
    phone: c.phone || '',
    address: c.address || '',
    active: c.active,
  };
}

async function assertUniqueName(name: string, exceptId?: string) {
  const clash = await prisma.client.findFirst({
    where: {
      name: { equals: name, mode: 'insensitive' },
      ...(exceptId ? { NOT: { id: exceptId } } : {}),
    },
  });
  if (clash) throw conflict(`Client "${name}" already exists`);
}

clientsRouter.get('/', async (req, res, next) => {
  try {
    const q = String(req.query.q || '').trim();
    const active =
      req.query.active === undefined
        ? undefined
        : req.query.active === 'true' || req.query.active === '1';
    const where: Prisma.ClientWhereInput = {};
    if (active !== undefined) where.active = active;
    if (q) {
      where.OR = [
        { name: { contains: q, mode: 'insensitive' } },
        { code: { contains: q, mode: 'insensitive' } },
        { contactName: { contains: q, mode: 'insensitive' } },
        { email: { contains: q, mode: 'insensitive' } },
        { phone: { contains: q, mode: 'insensitive' } },
      ];
    }
    const rows = await prisma.client.findMany({
      where,
      orderBy: [{ createdAt: 'desc' }, { name: 'asc' }],
    });
    res.json({ clients: rows.map(serializeClient) });
  } catch (err) {
    next(err);
  }
});

clientsRouter.get('/:id', async (req, res, next) => {
  try {
    const id = param(req.params.id);
    const row = await prisma.client.findFirst({
      where: { OR: [{ id }, { code: id }] },
    });
    if (!row) throw notFound('Client not found');
    res.json({ client: serializeClient(row) });
  } catch (err) {
    next(err);
  }
});

clientsRouter.post('/', requireSuperAdmin, async (req: AuthedRequest, res, next) => {
  try {
    const body = writeSchema.parse(req.body);
    await assertUniqueName(body.name);
    const code = await nextClientCode();
    const row = await prisma.client.create({
      data: {
        code,
        name: body.name,
        contactName: body.contactName || null,
        email: body.email || null,
        phone: body.phone || null,
        address: body.address || '',
        active: body.active !== false,
      },
    });
    res.status(201).json({ client: serializeClient(row) });
  } catch (err) {
    next(err);
  }
});

clientsRouter.patch('/:id', requireSuperAdmin, async (req: AuthedRequest, res, next) => {
  try {
    const id = param(req.params.id);
    const existing = await prisma.client.findUnique({ where: { id } });
    if (!existing) throw notFound('Client not found');
    const body = updateSchema.parse(req.body);
    if (body.name) await assertUniqueName(body.name, id);
    const row = await prisma.client.update({
      where: { id },
      data: {
        name: body.name,
        contactName: body.contactName === undefined ? undefined : body.contactName || null,
        email: body.email === undefined ? undefined : body.email || null,
        phone: body.phone === undefined ? undefined : body.phone || null,
        address: body.address === undefined ? undefined : body.address || '',
        active: body.active,
      },
    });
    res.json({ client: serializeClient(row) });
  } catch (err) {
    next(err);
  }
});

clientsRouter.post('/:id/activate', requireSuperAdmin, async (req, res, next) => {
  try {
    const id = param(req.params.id);
    const existing = await prisma.client.findUnique({ where: { id } });
    if (!existing) throw notFound('Client not found');
    const row = await prisma.client.update({ where: { id }, data: { active: true } });
    res.json({ client: serializeClient(row) });
  } catch (err) {
    next(err);
  }
});

clientsRouter.post('/:id/deactivate', requireSuperAdmin, async (req, res, next) => {
  try {
    const id = param(req.params.id);
    const existing = await prisma.client.findUnique({ where: { id } });
    if (!existing) throw notFound('Client not found');
    const mapped = await prisma.project.count({ where: { clientId: id } });
    const row = await prisma.client.update({ where: { id }, data: { active: false } });
    res.json({
      client: serializeClient(row),
      warning: mapped
        ? `${existing.name} is still mapped to ${mapped} project${mapped === 1 ? '' : 's'}.`
        : undefined,
    });
  } catch (err) {
    next(err);
  }
});

clientsRouter.delete('/:id', requireSuperAdmin, async (req, res, next) => {
  try {
    const id = param(req.params.id);
    const existing = await prisma.client.findUnique({ where: { id } });
    if (!existing) throw notFound('Client not found');
    const mapped = await prisma.project.count({ where: { clientId: id } });
    if (mapped) {
      throw conflict('This client is mapped to projects. Deactivate it instead.');
    }
    await prisma.client.delete({ where: { id } });
    res.json({ ok: true });
  } catch (err) {
    next(err);
  }
});

export async function assertAssignableClient(clientId: string | null | undefined, currentId?: string | null) {
  if (!clientId) return;
  const row = await prisma.client.findUnique({ where: { id: clientId } });
  if (!row) throw badRequest('Client not found');
  if (!row.active && clientId !== currentId) throw badRequest('Client is inactive');
}
