import { Router } from 'express';
import prisma from '../lib/prisma';
import {
  asyncHandler,
  sendSuccess,
  AppError,
  validate,
  paramId,
  getPagination,
  buildPaginationMeta,
} from '../lib/utils';
import { authenticate, authorize, AuthRequest } from '../middleware/auth';
import { createHireInvitationSchema } from '../validators/schemas';
import { createAndEmitNotification } from '../services/notification.service';
import { assertCustomerVerified } from '../lib/customerVerification';
import { resolveWorkerDisplayName, withPublicWorkerUser } from '../lib/workerDisplayName';

const router = Router();

const invitationInclude = {
  customer: {
    select: { id: true, firstName: true, lastName: true, avatarUrl: true, phone: true },
  },
  worker: {
    include: {
      user: {
        select: { id: true, firstName: true, lastName: true, avatarUrl: true, phone: true },
      },
      skills: { include: { category: { select: { id: true, name: true, slug: true } } } },
    },
  },
  review: { select: { id: true } },
} as const;

function personName(user: { firstName: string; lastName: string }) {
  return `${user.firstName} ${user.lastName}`.trim();
}

function publicInvitation<T extends { worker: Parameters<typeof withPublicWorkerUser>[0] }>(
  invitation: T
) {
  return {
    ...invitation,
    worker: withPublicWorkerUser(invitation.worker),
  };
}

router.post(
  '/',
  authenticate,
  authorize('CUSTOMER'),
  validate(createHireInvitationSchema),
  asyncHandler(async (req: AuthRequest, res) => {
    const { workerId, message, acknowledgedDisclaimer } = req.body;
    const customerId = req.user!.userId;

    await assertCustomerVerified(customerId, 'hire');

    const worker = await prisma.workerProfile.findFirst({
      where: { id: workerId, deletedAt: null },
      include: { user: { select: { id: true, firstName: true, lastName: true } } },
    });
    if (!worker) throw new AppError('Worker not found', 404);
    if (worker.userId === customerId) {
      throw new AppError('You cannot hire yourself', 400);
    }
    if (worker.isHired || !worker.isAvailable) {
      throw new AppError('This worker is currently hired or unavailable', 409);
    }

    const existingPending = await prisma.hireInvitation.findFirst({
      where: { customerId, workerId, status: 'PENDING' },
    });
    if (existingPending) {
      throw new AppError('You already have a pending hire invitation for this worker', 409);
    }

    const invitation = await prisma.hireInvitation.create({
      data: {
        customerId,
        workerId,
        message: typeof message === 'string' && message.trim() ? message.trim() : null,
        acknowledgedDisclaimer: Boolean(acknowledgedDisclaimer),
      },
      include: invitationInclude,
    });

    const customer = await prisma.user.findUnique({
      where: { id: customerId },
      select: { firstName: true, lastName: true },
    });
    const customerName = customer ? personName(customer) : 'A customer';

    await createAndEmitNotification(
      worker.userId,
      'HIRE_INVITE',
      'New hire invitation',
      `${customerName} wants to hire you. Accept or reject the offer.`,
      { hireInvitationId: invitation.id, workerId, customerId }
    );

    sendSuccess(res, publicInvitation(invitation), 'Hire invitation sent', 201);
  })
);

router.get(
  '/',
  authenticate,
  authorize('CUSTOMER', 'WORKER'),
  asyncHandler(async (req: AuthRequest, res) => {
    const { page, limit, skip } = getPagination(req.query);
    const status = typeof req.query.status === 'string' ? req.query.status : undefined;
    const role = req.user!.role;

    const where: Record<string, unknown> = {};
    if (status && ['PENDING', 'ACCEPTED', 'REJECTED', 'CANCELLED', 'COMPLETED'].includes(status)) {
      where.status = status;
    }

    if (role === 'WORKER') {
      const worker = await prisma.workerProfile.findUnique({
        where: { userId: req.user!.userId },
      });
      if (!worker) throw new AppError('Worker profile not found', 404);
      where.workerId = worker.id;
    } else {
      where.customerId = req.user!.userId;
    }

    const [items, total] = await Promise.all([
      prisma.hireInvitation.findMany({
        where,
        orderBy: { createdAt: 'desc' },
        skip,
        take: limit,
        include: invitationInclude,
      }),
      prisma.hireInvitation.count({ where }),
    ]);

    sendSuccess(res, items.map(publicInvitation), undefined, 200, buildPaginationMeta(total, page, limit));
  })
);

router.get(
  '/:id',
  authenticate,
  authorize('CUSTOMER', 'WORKER'),
  asyncHandler(async (req: AuthRequest, res) => {
    const invitation = await prisma.hireInvitation.findUnique({
      where: { id: paramId(req.params.id) },
      include: invitationInclude,
    });
    if (!invitation) throw new AppError('Hire invitation not found', 404);

    const role = req.user!.role;
    if (role === 'CUSTOMER' && invitation.customerId !== req.user!.userId) {
      throw new AppError('Not authorized', 403);
    }
    if (role === 'WORKER') {
      const worker = await prisma.workerProfile.findUnique({
        where: { userId: req.user!.userId },
      });
      if (!worker || invitation.workerId !== worker.id) {
        throw new AppError('Not authorized', 403);
      }
    }

    sendSuccess(res, publicInvitation(invitation));
  })
);

router.patch(
  '/:id/accept',
  authenticate,
  authorize('WORKER'),
  asyncHandler(async (req: AuthRequest, res) => {
    const worker = await prisma.workerProfile.findUnique({
      where: { userId: req.user!.userId },
    });
    if (!worker) throw new AppError('Worker profile not found', 404);

    const invitation = await prisma.hireInvitation.findUnique({
      where: { id: paramId(req.params.id) },
    });
    if (!invitation || invitation.workerId !== worker.id) {
      throw new AppError('Hire invitation not found', 404);
    }
    if (invitation.status !== 'PENDING') {
      throw new AppError('This invitation is no longer pending', 400);
    }
    if (worker.isHired) {
      throw new AppError('You are already marked as hired on another offer', 409);
    }

    const updated = await prisma.$transaction(async (tx) => {
      await tx.hireInvitation.updateMany({
        where: {
          workerId: worker.id,
          status: 'PENDING',
          id: { not: invitation.id },
        },
        data: { status: 'CANCELLED', respondedAt: new Date() },
      });

      const accepted = await tx.hireInvitation.update({
        where: { id: invitation.id },
        data: { status: 'ACCEPTED', respondedAt: new Date() },
        include: invitationInclude,
      });

      await tx.workerProfile.update({
        where: { id: worker.id },
        data: { isHired: true, isAvailable: false },
      });

      return accepted;
    });

    const workerName = resolveWorkerDisplayName(updated.worker.displayName, updated.worker.user);
    await createAndEmitNotification(
      invitation.customerId,
      'HIRE_ACCEPTED',
      'Hire invitation accepted',
      `${workerName} accepted your hire invitation and is now marked as hired.`,
      { hireInvitationId: invitation.id, workerId: worker.id }
    );

    sendSuccess(res, publicInvitation(updated), 'Hire invitation accepted');
  })
);

router.patch(
  '/:id/reject',
  authenticate,
  authorize('WORKER'),
  asyncHandler(async (req: AuthRequest, res) => {
    const worker = await prisma.workerProfile.findUnique({
      where: { userId: req.user!.userId },
      include: { user: { select: { firstName: true, lastName: true } } },
    });
    if (!worker) throw new AppError('Worker profile not found', 404);

    const invitation = await prisma.hireInvitation.findUnique({
      where: { id: paramId(req.params.id) },
    });
    if (!invitation || invitation.workerId !== worker.id) {
      throw new AppError('Hire invitation not found', 404);
    }
    if (invitation.status !== 'PENDING') {
      throw new AppError('This invitation is no longer pending', 400);
    }

    const updated = await prisma.hireInvitation.update({
      where: { id: invitation.id },
      data: { status: 'REJECTED', respondedAt: new Date() },
      include: invitationInclude,
    });

    await createAndEmitNotification(
      invitation.customerId,
      'HIRE_REJECTED',
      'Hire invitation declined',
      `${resolveWorkerDisplayName(worker.displayName, worker.user)} declined your hire invitation.`,
      { hireInvitationId: invitation.id, workerId: worker.id }
    );

    sendSuccess(res, publicInvitation(updated), 'Hire invitation rejected');
  })
);

router.patch(
  '/:id/cancel',
  authenticate,
  authorize('CUSTOMER'),
  asyncHandler(async (req: AuthRequest, res) => {
    const invitation = await prisma.hireInvitation.findUnique({
      where: { id: paramId(req.params.id) },
    });
    if (!invitation || invitation.customerId !== req.user!.userId) {
      throw new AppError('Hire invitation not found', 404);
    }
    if (invitation.status !== 'PENDING') {
      throw new AppError('Only pending invitations can be cancelled', 400);
    }

    const updated = await prisma.hireInvitation.update({
      where: { id: invitation.id },
      data: { status: 'CANCELLED', respondedAt: new Date() },
      include: invitationInclude,
    });

    sendSuccess(res, publicInvitation(updated), 'Hire invitation cancelled');
  })
);

router.patch(
  '/:id/complete',
  authenticate,
  authorize('CUSTOMER', 'WORKER'),
  asyncHandler(async (req: AuthRequest, res) => {
    const invitation = await prisma.hireInvitation.findUnique({
      where: { id: paramId(req.params.id) },
      include: invitationInclude,
    });
    if (!invitation) throw new AppError('Hire invitation not found', 404);
    if (invitation.status !== 'ACCEPTED') {
      throw new AppError('Only accepted hires can be completed', 400);
    }

    const role = req.user!.role;
    if (role === 'CUSTOMER' && invitation.customerId !== req.user!.userId) {
      throw new AppError('Not authorized', 403);
    }
    if (role === 'WORKER') {
      const worker = await prisma.workerProfile.findUnique({
        where: { userId: req.user!.userId },
      });
      if (!worker || invitation.workerId !== worker.id) {
        throw new AppError('Not authorized', 403);
      }
    }

    const updated = await prisma.$transaction(async (tx) => {
      const completed = await tx.hireInvitation.update({
        where: { id: invitation.id },
        data: { status: 'COMPLETED', completedAt: new Date() },
        include: invitationInclude,
      });

      await tx.workerProfile.update({
        where: { id: invitation.workerId },
        data: {
          isHired: false,
          isAvailable: true,
          totalJobs: { increment: 1 },
        },
      });

      return completed;
    });

    const notifyUserId =
      role === 'CUSTOMER' ? invitation.worker.user.id : invitation.customerId;
    const actorName =
      role === 'CUSTOMER'
        ? personName(invitation.customer)
        : resolveWorkerDisplayName(invitation.worker.displayName, invitation.worker.user);

    await createAndEmitNotification(
      notifyUserId,
      'HIRE_COMPLETED',
      'Hire marked complete',
      `${actorName} marked the hire as complete. The worker is available again.`,
      { hireInvitationId: invitation.id, workerId: invitation.workerId }
    );

    sendSuccess(res, publicInvitation(updated), 'Hire completed — worker is available again');
  })
);

export default router;
