import {
  BadRequestError,
  NotFoundError,
} from "../../common/errors/http-errors";
import { prisma } from "../../config/prisma";
import * as taskRepo from "./task.repo";
import {
  ApprovalStatus,
  DocumentStatus,
  Priority,
  TaskStatus,
} from "@prisma/client";

import { mailService } from "../../infrastructure/mail/mail.service";
import { env } from "../../config/env";
import { logger } from "../../common/utils/logger";
import { generateInvoicePaymentLink } from "../payment/payment.service";
import * as notificationService from "../notifications/notifications.service";
import { emitTaskStatusUpdate } from "../../config/socket";

async function getClientContext(userId: string) {
  const client = await prisma.client.findUnique({
    where: { userId },
    select: { id: true, organizationId: true, name: true },
  });
  if (!client) throw new BadRequestError("Client profile not found");
  return client;
}

export async function createTaskWithDocuments(
  userId: string,
  body: {
    serviceId?: string;
    title?: string;
    description?: string;
    dueDate?: string;
  },
  files: Express.Multer.File[],
) {
  const { serviceId, title, description, dueDate } = body;
  if (!serviceId) throw new BadRequestError("Service is required");
  if (!title?.trim()) throw new BadRequestError("Title is required");

  const client = await getClientContext(userId);

  const service = await prisma.service.findUnique({
    where: { id: serviceId },
  });
  if (!service || service.organizationId !== client.organizationId)
    throw new NotFoundError("Service not found");

  const serviceDocuments = await prisma.serviceDocument.findMany({
    where: { serviceId },
    select: { id: true, name: true, isRequired: true },
  });

  const requiredDocs = serviceDocuments.filter((d) => d.isRequired);
  const validServiceDocIds = new Set(serviceDocuments.map((d) => d.id));

  const providedServiceDocIds = new Set(
    files
      .filter((f: any) => validServiceDocIds.has(f.fieldname))
      .map((f: any) => f.fieldname),
  );
  const missingRequiredDocuments = requiredDocs.filter(
    (d) => !providedServiceDocIds.has(d.id),
  );

  // BLOCK task creation if any required document is missing
  if (missingRequiredDocuments.length > 0) {
    throw new BadRequestError(
      `Please upload all required documents: ${missingRequiredDocuments.map((d) => d.name).join(", ")}`,
    );
  }

  const year = new Date().getFullYear();
  const folder = await taskRepo.getOrCreateYearFolder(client.id, year);

  const documents = files.map((file: any) => ({
    serviceDocumentId: validServiceDocIds.has(file.fieldname)
      ? file.fieldname
      : null,
    fileName: file.originalname,
    fileUrl: file.path, // Cloudinary secure_url
    fileType: file.mimetype,
    fileSizeKb: Math.round(file.size / 1024),
    uploadedByUserId: userId,
  }));

  const task = await taskRepo.createTaskWithDocuments({
    organizationId: client.organizationId,
    clientId: client.id,
    serviceId,
    title: title.trim(),
    description: description?.trim() || undefined,
    dueDate: dueDate ? new Date(dueDate) : undefined,
    folderId: folder.id,
    documents,
  });

  if (!task) throw new BadRequestError("Failed to create task");

  // ── NEW: notify admins that a client created a new task ──
  const admins = await prisma.user.findMany({
    where: {
      organizationId: client.organizationId,
      role: { in: ["ADMIN", "SUPER_ADMIN"] },
    },
    select: { id: true },
  });

  await Promise.all(
    admins.map((admin) =>
      notificationService.sendEmailNotification({
        userId: admin.id,
        title: "New Task Created",
        message: `A new "${task.title}" task for ${service.name} has been created by ${client.name}. It's currently pending review — please assign it to the appropriate team member and ensure it's processed within the required timeline.`,
        actionUrl: `${env.FRONTEND_URL}/admin/tasks`,
        actionLabel: "View Task",
      }),
    ),
  );

  return { task };
}

export async function getTasks(
  userId: string,
  page: number,
  limit: number,
  search: string,
  status?: TaskStatus,
  priority?: Priority,
) {
  const client = await getClientContext(userId);
  return taskRepo.findTasksByClient(
    client.id,
    page,
    limit,
    search,
    status,
    priority,
  );
}

export async function clientDecideTask(
  userId: string,
  taskId: string,
  body: {
    status: Extract<ApprovalStatus, "APPROVED" | "REJECTED">;
    remarks?: string;
  },
) {
  const client = await getClientContext(userId);

  const task = await taskRepo.findClientTaskForApproval(taskId, client.id);
  if (!task) throw new NotFoundError("Task not found");

  if (body.status === ApprovalStatus.REJECTED && !body.remarks?.trim())
    throw new BadRequestError("Remarks are required to reject a task");

  const result = await taskRepo.decideTaskByClient(
    taskId,
    client.id,
    body.status,
    body.remarks?.trim(),
  );
  const admins = await prisma.user.findMany({
    where: {
      organizationId: client.organizationId,
      role: { in: ["ADMIN", "SUPER_ADMIN"] },
    },
    select: { id: true },
  });

  await Promise.all(
    admins.map((admin) =>
      notificationService.sendEmailNotification({
        userId: admin.id,
        title:
          body.status === "APPROVED"
            ? "Client Approved Task"
            : "Client Rejected Task",
        message:
          body.status === ApprovalStatus.APPROVED
            ? `The client has approved the task "${task.title}". The task is now ready for the next stage of processing.`
            : `The client has rejected the task "${task.title}". Please review the client's remarks and take the necessary action.`,
        actionUrl: `${env.FRONTEND_URL}/admin/tasks`,
        actionLabel: "View Task",
      }),
    ),
  );

  return result;
}

export async function getTask(userId: string, taskId: string) {
  const client = await getClientContext(userId);
  const task = await taskRepo.findTaskById(taskId, client.id);
  if (!task) throw new NotFoundError("Task not found");
  return task;
}

// NEW
export async function updateTask(
  userId: string,
  taskId: string,
  body: {
    title?: string;
    description?: string;
    dueDate?: string;
    status?: TaskStatus;
    removeDocumentIds?: string; // JSON array string OR comma-separated ids
  },
  files: Express.Multer.File[],
) {
  const client = await getClientContext(userId);

  const existing = await taskRepo.findTaskById(taskId, client.id);
  if (!existing) throw new NotFoundError("Task not found");

  let removeIds: string[] = [];
  if (body.removeDocumentIds) {
    try {
      removeIds = JSON.parse(body.removeDocumentIds);
    } catch {
      removeIds = body.removeDocumentIds
        .split(",")
        .map((s) => s.trim())
        .filter(Boolean);
    }
  }

  const validServiceDocIds = new Set(
    (
      await prisma.serviceDocument.findMany({
        where: { serviceId: existing.serviceId },
        select: { id: true },
      })
    ).map((d) => d.id),
  );

  let folderId: string | null = null;
  let newDocuments: any[] = [];
  if (files?.length) {
    const year = new Date().getFullYear();
    const folder = await taskRepo.getOrCreateYearFolder(client.id, year);
    folderId = folder.id;

    const acceptedFiles = files.filter((file: any) => {
      const fieldName = String(file.fieldname ?? "");
      if (!fieldName) return false;

      // Only service-document keys that belong to this exact service are accepted.
      // Everything else is either an optional "other" file or a stale/invalid field name.
      if (fieldName === "other") return true;
      if (validServiceDocIds.has(fieldName)) return true;

      logger.warn(
        {
          taskId,
          clientId: client.id,
          serviceId: existing.serviceId,
          uploadedField: fieldName,
        },
        "Ignoring task update upload with foreign serviceDocumentId",
      );
      return false;
    });

    newDocuments = acceptedFiles.map((file: any) => ({
      serviceDocumentId: validServiceDocIds.has(file.fieldname)
        ? file.fieldname
        : null,
      fileName: file.originalname,
      fileUrl: file.path,
      fileType: file.mimetype,
      fileSizeKb: Math.round(file.size / 1024),
      uploadedByUserId: userId,
    }));
  }

  const uploadServiceDocIds = new Set(
    newDocuments
      .map((d) => d.serviceDocumentId)
      .filter((id): id is string => !!id),
  );
  if (uploadServiceDocIds.size) {
    const existingForSlots = await prisma.document.findMany({
      where: { taskId, serviceDocumentId: { in: [...uploadServiceDocIds] } },
      select: { id: true },
    });
    removeIds = Array.from(
      new Set([...removeIds, ...existingForSlots.map((d) => d.id)]),
    );
  }

  const result = await taskRepo.updateTaskWithDocuments(
    taskId,
    client.id,
    {
      title: body.title?.trim() || undefined,
      description: body.description?.trim(),
      dueDate: body.dueDate ? new Date(body.dueDate) : undefined,
      status: body.status,
    },
    folderId,
    newDocuments,
    removeIds,
  );
  const recipientUserIds: string[] = [];

  if (existing.assignedEmployeeId) {
    const emp = await prisma.employee.findUnique({
      where: { id: existing.assignedEmployeeId },
      select: { userId: true },
    });
    if (emp?.userId) recipientUserIds.push(emp.userId);
  } else {
    const admins = await prisma.user.findMany({
      where: {
        organizationId: existing.organizationId,
        role: { in: ["ADMIN", "SUPER_ADMIN"] },
      },
      select: { id: true },
    });
    recipientUserIds.push(...admins.map((a) => a.id));
  }

  await Promise.all(
    recipientUserIds.map((uid) =>
      notificationService.sendEmailNotification({
        userId: uid,
        title: "Task Updated by Client",
        message: `The client has updated the task "${existing.title}". Please review the latest details and documents.`,
        actionUrl: `${env.FRONTEND_URL}/admin/tasks/${taskId}`,
        actionLabel: "View Task",
      }),
    ),
  );
}

// NEW
// export async function deleteTask(userId: string, taskId: string) {
//   const client = await getClientContext(userId);

//   const existing = await taskRepo.findTaskById(taskId, client.id);
//   if (!existing) throw new NotFoundError("Task not found");

//   await taskRepo.deleteTask(taskId);
// }

export async function deleteTask(userId: string, taskId: string) {
  const client = await getClientContext(userId);

  const existing = await taskRepo.findTaskById(taskId, client.id);
  if (!existing) throw new NotFoundError("Task not found");

  await taskRepo.deleteTask(taskId);

  const admins = await prisma.user.findMany({
    where: {
      organizationId: client.organizationId,
      role: { in: ["ADMIN", "SUPER_ADMIN"] },
    },
    select: { id: true },
  });

  await Promise.all(
    admins.map((admin) =>
      notificationService.sendEmailNotification({
        userId: admin.id,
        title: "Task Deleted by Client",
        message: `${client.name} has deleted the task "${existing.title}".`,
      }),
    ),
  );
}

// ── ADMIN ────────────────────────────────────────────────────

async function getAdminContext(userId: string) {
  const user = await prisma.user.findUnique({
    where: { id: userId },
    select: { organizationId: true, role: true },
  });
  if (!user?.organizationId)
    throw new BadRequestError("Admin organization not found");
  if (!["ADMIN", "SUPER_ADMIN"].includes(user.role))
    throw new BadRequestError("Access denied");
  return { organizationId: user.organizationId };
}

// GST_RATE and invoice numbering are placeholders — adjust to your real business rules.
const GST_RATE = 0.18;

async function generateInvoiceNumber() {
  const count = await prisma.invoice.count();
  const year = new Date().getFullYear();
  return `INV-${year}-${String(count + 1).padStart(5, "0")}`;
}

async function createInvoiceForCompletedTask(taskId: string) {
  const task = await prisma.task.findUnique({
    where: { id: taskId },
    include: { service: true },
  });
  if (!task) return null;

  // Invoice.taskId is @unique — don't create a second one if this task already has an invoice
  const existingInvoice = await prisma.invoice.findUnique({
    where: { taskId },
  });
  if (existingInvoice) return existingInvoice;

  const subtotal = task.service.price;
  const gstAmount = Number(subtotal) * GST_RATE;
  const totalAmount = Number(subtotal) + gstAmount;
  const invoiceNumber = await generateInvoiceNumber();

  return prisma.$transaction(async (tx) => {
    const invoice = await tx.invoice.create({
      data: {
        clientId: task.clientId,
        taskId: task.id,
        invoiceNumber,
        subtotal,
        gstAmount,
        totalAmount,
        status: "SENT",
      },
    });

    await tx.invoiceItem.create({
      data: {
        invoiceId: invoice.id,
        serviceId: task.serviceId,
        quantity: 1,
        price: subtotal,
      },
    });

    return invoice;
  });
}

export async function adminGetTasks(
  userId: string,
  page: number,
  limit: number,
  filters: {
    status?: TaskStatus;
    search?: string;
    clientId?: string;
    departmentId?: string;
    assignedEmployeeId?: string;
    priority?: string;
  },
) {
  const { organizationId } = await getAdminContext(userId);
  return taskRepo.findTasksByOrg(organizationId, page, limit, filters);
}

export async function adminGetTask(userId: string, taskId: string) {
  const { organizationId } = await getAdminContext(userId);
  const task = await taskRepo.findTaskByIdForAdmin(taskId, organizationId);
  if (!task) throw new NotFoundError("Task not found");
  return task;
}

export async function adminUpdateTask(
  userId: string,
  taskId: string,
  body: {
    status?: TaskStatus;
    assignedEmployeeId?: string;
    departmentId?: string;
    priority?: string;
    dueDate?: string;
    title?: string;
    description?: string;
  },
) {
  const { organizationId } = await getAdminContext(userId);
  const existing = await taskRepo.findTaskByIdForAdmin(taskId, organizationId);
  if (!existing) throw new NotFoundError("Task not found");

  // Validate employee belongs to this org if assigning
  if (body.assignedEmployeeId) {
    const emp = await prisma.employee.findFirst({
      where: { id: body.assignedEmployeeId, organizationId, status: "ACTIVE" },
    });
    if (!emp)
      throw new BadRequestError(
        "Employee not found or not active in your organization",
      );
  }
  if (
    existing.status === TaskStatus.COMPLETED &&
    body.status &&
    body.status !== TaskStatus.COMPLETED
  ) {
    throw new BadRequestError(
      "This task is already completed and paid. Status cannot be changed.",
    );
  }

  const updated = await taskRepo.adminUpdateTask(taskId, {
    status: body.status,
    assignedEmployeeId: body.assignedEmployeeId ?? undefined,
    departmentId: body.departmentId ?? undefined,
    priority: body.priority as any,
    dueDate: body.dueDate ? new Date(body.dueDate) : undefined,
    title: body.title?.trim(),
    description: body.description?.trim(),
  });

  if (
    body.assignedEmployeeId &&
    body.assignedEmployeeId !== existing.assignedEmployeeId
  ) {
    const emp = await prisma.employee.findUnique({
      where: { id: body.assignedEmployeeId },
      select: { userId: true },
    });

    if (emp?.userId) {
      await notificationService.sendEmailNotification({
        userId: emp.userId,
        title: "Task Assigned",
        message: `A new task "${existing.title}" has been assigned to you. Please review the details, check any attached documents, and ensure it is completed within the given timeline.`,
        actionUrl: `${env.FRONTEND_URL}/employee/tasks`,
        actionLabel: "View Task",
      });
    }
  }

  if (body.status === TaskStatus.COMPLETED) {
    const invoice = await createInvoiceForCompletedTask(taskId);

    let invoiceUrl: string | undefined;
    if (invoice) {
      try {
        invoiceUrl = await generateInvoicePaymentLink(invoice.id);
      } catch (error) {
        logger.warn(
          { error, invoiceId: invoice.id },
          "Failed to generate Stripe payment link for invoice",
        );
      }
    }

    const fullTask = await prisma.task.findUnique({
      where: { id: taskId },
      include: {
        client: { select: { name: true, email: true, phone: true } },
        service: { select: { name: true } },
        documents: { select: { fileName: true, fileUrl: true } },
        invoice: {
          select: {
            id: true,
            invoiceNumber: true,
            subtotal: true,
            gstAmount: true,
            totalAmount: true,
            paymentLinkUrl: true,
          },
        },
      },
    });

    if (fullTask?.client?.email) {
      const org = await prisma.organization.findUnique({
        where: { id: existing.organizationId },
        select: { name: true },
      });

      const clientWithUserId = await prisma.client.findUnique({
        where: { id: fullTask.clientId },
        select: { userId: true },
      });

      if (clientWithUserId?.userId) {
        await notificationService.sendEmailNotification({
          userId: clientWithUserId.userId,
          title: "Task Completed",
          message: `Your task "${fullTask.title}" for ${fullTask.service.name} has been completed successfully. Your invoice is ready — please review and proceed with the payment at your earliest convenience.`,
          skipEmail: true,
        });
      }

      await mailService.sendTaskCompletedEmail({
        to: fullTask.client.email,
        clientName: fullTask.client.name,
        clientPhone: fullTask.client.phone ?? undefined,
        taskTitle: fullTask.title,
        serviceName: fullTask.service.name,
        completedAt: new Date().toLocaleDateString("en-IN", {
          day: "2-digit",
          month: "long",
          year: "numeric",
        }),
        documents: fullTask.documents,
        invoice: fullTask.invoice
          ? {
              invoiceNumber: fullTask.invoice.invoiceNumber,
              subtotal: fullTask.invoice.subtotal.toString(),
              gstAmount: fullTask.invoice.gstAmount?.toString(),
              totalAmount: fullTask.invoice.totalAmount.toString(),
            }
          : undefined,
        companyName: org?.name ?? "TaxFend",
        taskUrl: `${env.FRONTEND_URL}/client/tasks`,
        invoiceUrl,
      });
      console.log(fullTask?.invoice);
    }
  }

  // emit real-time update
  emitTaskStatusUpdate(existing.organizationId, {
    taskId,
    status: body.status ?? existing.status,
    clientId: existing.client?.id,
  });

  return updated;
}

export async function adminGetActiveEmployees(userId: string) {
  const { organizationId } = await getAdminContext(userId);
  return taskRepo.findActiveEmployeesByOrg(organizationId);
}

// get task assigned to employee
export async function getTasksemp(
  userId: string,
  page: number,
  limit: number,
  status?: TaskStatus,
) {
  const employee = await prisma.employee.findUnique({
    where: { userId },
    select: { id: true, organizationId: true },
  });
  if (!employee) throw new BadRequestError("Employee profile not found");

  const tasks = await prisma.task.findMany({
    where: {
      assignedEmployeeId: employee.id,
      ...(status ? { status } : {}),
    },
    skip: (page - 1) * limit,
    take: limit,
    orderBy: { createdAt: "desc" },
  });

  const total = await prisma.task.count({
    where: {
      assignedEmployeeId: employee.id,
      ...(status ? { status } : {}),
    },
  });

  return { data: tasks, total };
}

export async function getEmployeeTasks(
  userId: string,
  page: number,
  limit: number,
  search: string,
  status?: TaskStatus,
  priority?: Priority,
) {
  const employee = await taskRepo.findEmployeeByUserId(userId);

  if (!employee) {
    throw new BadRequestError("Employee profile not found");
  }

  return taskRepo.findTasksByEmployee(
    employee.id,
    page,
    limit,
    search,
    status,
    priority,
  );
}

export async function getEmployeeTask(userId: string, taskId: string) {
  const employee = await taskRepo.findEmployeeByUserId(userId);

  if (!employee) {
    throw new BadRequestError("Employee profile not found");
  }

  const task = await taskRepo.findTaskForEmployee(taskId, employee.id);

  if (!task) {
    throw new NotFoundError("Task not found");
  }

  return task;
}

export async function taskApproval(
  user: any,
  taskId: string,
  payload: {
    documentIds: string[];
    status: Extract<DocumentStatus, "VERIFIED" | "REJECTED">;
    remarks?: string;
  },
) {
  const employee = await taskRepo.findEmployeeByUserId(user.id);

  if (!employee) {
    throw new BadRequestError("Employee profile not found");
  }

  const task = await taskRepo.findEmployeeTask(taskId, employee.id);

  if (!task) {
    throw new NotFoundError("Task not found");
  }

  const result = await taskRepo.verifyDocuments(
    taskId,
    payload.documentIds,
    payload.status,
    payload.remarks,
  );
  // ── Notify client when employee rejects document(s) ──
  const fullTask = await prisma.task.findUnique({
    where: { id: taskId },
    include: {
      client: { select: { userId: true, name: true, email: true } },
      service: { select: { name: true } },
      documents: {
        where: { id: { in: payload.documentIds } },
        select: { fileName: true },
      },
    },
  });

  if (fullTask?.client) {
    const org = await prisma.organization.findUnique({
      where: { id: task.organizationId },
      select: { name: true },
    });
  }

  if (fullTask?.client?.userId) {
    const fileNames = fullTask.documents.map((d) => d.fileName).join(", ");

    if (payload.status === DocumentStatus.REJECTED) {
      await notificationService.sendEmailNotification({
        userId: fullTask.client.userId,
        title: "Document Rejected",
        message: `Your document(s) (${fileNames}) for task "${fullTask.title}" (${fullTask.service.name}) were rejected. ${
          payload.remarks?.trim() ?? "Please re-upload the correct file(s)."
        }`,
        actionUrl: `${env.FRONTEND_URL}/client/tasks/${taskId}`,
        actionLabel: "View Task",
      });
    }

    if (payload.status === DocumentStatus.VERIFIED) {
      await notificationService.sendEmailNotification({
        userId: fullTask.client.userId,
        title: "Document Verified",
        message: `Your document(s) (${fileNames}) for task "${fullTask.title}" (${fullTask.service.name}) have been verified.`,
        actionUrl: `${env.FRONTEND_URL}/client/tasks/${taskId}`,
        actionLabel: "View Task",
        skipEmail: true, // sirf in-app notification, email nahi
      });
    }
  }

  return result;
}

// export async function adminDeleteTask(userId: string, taskId: string) {
//   const { organizationId } = await getAdminContext(userId);
//   const result = await taskRepo.adminDeleteTask(taskId, organizationId);
//   if (!result) throw new NotFoundError("Task not found");
// }

export async function adminDeleteTask(userId: string, taskId: string) {
  const { organizationId } = await getAdminContext(userId);
  const task = await prisma.task.findFirst({
    where: { id: taskId, organizationId },
    include: {
      client: { select: { userId: true, name: true, email: true } },
      service: { select: { name: true } },
    },
  });
  if (!task) throw new NotFoundError("Task not found");

  const result = await taskRepo.adminDeleteTask(taskId, organizationId);
  if (!result) throw new NotFoundError("Task not found");

  if (task.client?.userId) {
    await notificationService.sendEmailNotification({
      userId: task.client.userId,
      title: "Task Deleted",
      message: `Your task "${task.title}" (${task.service.name}) has been deleted by the admin. If you believe this was a mistake, please contact support.`,
    });
  }
}

export async function adminGetActiveClients(
  userId: string,
  search?: string,
  page = 1,
  limit = 10,
) {
  const { organizationId } = await getAdminContext(userId);
  return taskRepo.findActiveClientsByOrg(organizationId, search, page, limit);
}

export async function adminGetEmployeesByDepartment(
  userId: string,
  departmentId: string,
  search?: string,
) {
  const { organizationId } = await getAdminContext(userId);
  return taskRepo.findActiveEmployeesByDepartment(
    organizationId,
    departmentId,
    search,
  );
}

export async function adminGetTaskStats(userId: string) {
  const { organizationId } = await getAdminContext(userId);
  const PENDING_STATUSES: TaskStatus[] = [
    TaskStatus.PENDING,
    TaskStatus.IN_PROGRESS,
    TaskStatus.EMPLOYEE_DONE,
    TaskStatus.MANAGER_APPROVAL,
    TaskStatus.CLIENT_APPROVAL,
    TaskStatus.FILING_PENDING,
  ];
  const [pending, completed, rejected] = await Promise.all([
    prisma.task.count({
      where: { organizationId, status: { in: [...PENDING_STATUSES] } },
    }),
    prisma.task.count({ where: { organizationId, status: "COMPLETED" } }),
    prisma.task.count({ where: { organizationId, status: "REJECTED" } }),
  ]);
  return { pending, completed, rejected };
}

export async function adminCreateTask(
  userId: string,
  body: {
    clientId?: string;
    serviceId?: string;
    departmentId?: string;
    assignedEmployeeId?: string;
    title?: string;
    description?: string;
    dueDate?: string;
    priority?: string;
  },
  files: Express.Multer.File[],
) {
  if (!body.clientId) throw new BadRequestError("Client is required");
  if (!body.serviceId) throw new BadRequestError("Service is required");
  if (!body.title?.trim()) throw new BadRequestError("Title is required");

  const { organizationId } = await getAdminContext(userId);

  const client = await prisma.client.findFirst({
    where: { id: body.clientId, organizationId },
  });
  if (!client) throw new NotFoundError("Client not found");

  const service = await prisma.service.findFirst({
    where: { id: body.serviceId, organizationId },
    include: { serviceDocuments: true },
  });
  if (!service) throw new NotFoundError("Service not found");

  if (body.assignedEmployeeId) {
    const emp = await prisma.employee.findFirst({
      where: { id: body.assignedEmployeeId, organizationId, status: "ACTIVE" },
    });
    if (!emp) throw new BadRequestError("Employee not found or not active");
  }

  const validServiceDocIds = new Set(service.serviceDocuments.map((d) => d.id));

  // Validate required documents
  const requiredDocs = service.serviceDocuments.filter((d) => d.isRequired);
  const uploadedDocIds = new Set(files.map((f: any) => f.fieldname));
  const missingDocs = requiredDocs.filter((d) => !uploadedDocIds.has(d.id));
  if (missingDocs.length > 0) {
    throw new BadRequestError(
      `Please upload all required documents: ${missingDocs.map((d) => d.name).join(", ")}`,
    );
  }

  const year = new Date().getFullYear();
  const folder = await taskRepo.getOrCreateYearFolder(body.clientId, year);

  const documents = files.map((file: any) => ({
    serviceDocumentId: validServiceDocIds.has(file.fieldname)
      ? file.fieldname
      : null,
    fileName: file.originalname,
    fileUrl: file.path,
    fileType: file.mimetype,
    fileSizeKb: Math.round(file.size / 1024),
    uploadedByUserId: userId,
  }));

  const task = await taskRepo.adminCreateTaskWithDocuments({
    organizationId,
    clientId: body.clientId,
    serviceId: body.serviceId,
    departmentId: body.departmentId,
    assignedEmployeeId: body.assignedEmployeeId,
    title: body.title.trim(),
    description: body.description?.trim(),
    dueDate: body.dueDate ? new Date(body.dueDate) : undefined,
    priority: body.priority as any,
    folderId: folder.id,
    documents,
  });

  if (body.assignedEmployeeId) {
    const emp = await prisma.employee.findUnique({
      where: { id: body.assignedEmployeeId },
      select: { userId: true },
    });
    if (emp?.userId) {
      await notificationService.sendEmailNotification({
        userId: emp.userId,
        title: "New Task Assigned",
        message: `You have been assigned a new task: "${body.title.trim()}"`,
      });
    }
  }

  return task;
}

// Employee approves the task after verifying all documents

export async function employeeApproveTask(
  user: any,
  taskId: string,
  body: { remarks?: string },
) {
  const employee = await taskRepo.findEmployeeByUserId(user.id);
  if (!employee) throw new BadRequestError("Employee profile not found");

  const task = await taskRepo.findEmployeeTask(taskId, employee.id);
  if (!task) throw new NotFoundError("Task not found");

  if (task.status !== TaskStatus.EMPLOYEE_DONE) {
    throw new BadRequestError(
      "Please verify all documents before approving this task",
    );
  }

  const result = await taskRepo.approveTaskByEmployee(
    taskId,
    employee.id,
    body.remarks?.trim(),
  );

  const fullTask = await prisma.task.findUnique({
    where: { id: taskId },
    select: { title: true, departmentId: true, organizationId: true },
  });

  if (fullTask?.departmentId) {
    const managers = await prisma.employee.findMany({
      where: {
        organizationId: fullTask.organizationId,
        departmentId: fullTask.departmentId,
        role: "MANAGER",
        status: "ACTIVE",
      },
      select: { userId: true },
    });

    await Promise.all(
      managers
        .filter((m) => m.userId)
        .map((m) =>
          notificationService.sendEmailNotification({
            userId: m.userId!,
            title: "Task Ready for Review",
            message: `Task "${fullTask.title}" has been completed by the employee and is ready for your approval.`,
            actionUrl: `${env.FRONTEND_URL}/manager/tasks`,
            actionLabel: "Review Task",
          }),
        ),
    );
  }

  // emit real-time update
  emitTaskStatusUpdate(result.organizationId, {
    taskId,
    status: result.status,
    clientId: result.clientId,
  });

  return result;
}

async function getManagerContext(userId: string) {
  const manager = await taskRepo.findManagerByUserId(userId);
  if (!manager) throw new BadRequestError("Manager profile not found");
  if (manager.role !== "MANAGER" && manager.role !== "ADMIN") {
    throw new BadRequestError("Access denied");
  }
  return manager;
}

export async function getManagerTasks(
  userId: string,
  page: number,
  limit: number,
  search: string,
  status?: TaskStatus,
  priority?: Priority,
) {
  const manager = await getManagerContext(userId);
  return taskRepo.findTasksForManager(
    manager.organizationId,
    page,
    limit,
    search,
    status,
    // status ?? TaskStatus.MANAGER_APPROVAL,
    manager.departmentId,
    priority,
  );
}

export async function getManagerTask(userId: string, taskId: string) {
  const manager = await getManagerContext(userId);

  const task = await taskRepo.findTaskForManager(
    taskId,
    manager.organizationId,
    manager.role === "ADMIN" ? undefined : manager.departmentId,
  );

  if (!task) {
    throw new NotFoundError("Task not found");
  }

  return task;
}

// managerDecideTask

// dont rempove thie comment code
// export async function managerDecideTask(
//   userId: string,
//   taskId: string,
//   body: {
//     status: Extract<ApprovalStatus, "APPROVED" | "REJECTED">;
//     remarks?: string;
//   },
// ) {
//   const manager = await getManagerContext(userId);

//   const task = await taskRepo.findManagerTask(
//     taskId,
//     manager.organizationId,
//     manager.role === "ADMIN" ? undefined : manager.departmentId,
//   );
//   if (!task) throw new NotFoundError("Task not found");

//   if (body.status === ApprovalStatus.REJECTED && !body.remarks?.trim())
//     throw new BadRequestError("Remarks are required to reject a task");

//   const updated = await taskRepo.decideTaskByManager(
//     taskId,
//     manager.id,
//     body.status,
//     body.remarks?.trim(),
//   );

//   //  send rejection email to client
// if (body.status === ApprovalStatus.REJECTED) {
//   const fullTask = await prisma.task.findUnique({
//     where: { id: taskId },
//     include: {
//       client: { select: { name: true, email: true } },
//       service: { select: { name: true } },
//     },
//   });

//   if (fullTask?.client?.email) {
//     const org = await prisma.organization.findUnique({
//       where: { id: manager.organizationId },
//       select: { name: true },
//     });

//     await mailService.sendTaskRejectedEmail({
//       to: fullTask.client.email,
//       clientName: fullTask.client.name,
//       taskTitle: fullTask.title,
//       serviceName: fullTask.service.name,
//       remarks: body.remarks?.trim(),
//       rejectedAt: new Date().toLocaleDateString("en-IN", {
//         day: "2-digit",
//         month: "long",
//         year: "numeric",
//       }),
//       companyName: org?.name ?? "TaxFend",
//       taskUrl: `${env.FRONTEND_URL}/client/tasks`,
//     });
//   }
// }

//   return updated;
// }

export async function managerDecideTask(
  userId: string,
  taskId: string,
  body: {
    status: Extract<ApprovalStatus, "APPROVED" | "REJECTED">;
    remarks?: string;
  },
) {
  const manager = await getManagerContext(userId);

  const task = await taskRepo.findManagerTask(
    taskId,
    manager.organizationId,
    manager.role === "ADMIN" ? undefined : manager.departmentId,
  );
  if (!task) throw new NotFoundError("Task not found");

  if (body.status === ApprovalStatus.REJECTED && !body.remarks?.trim())
    throw new BadRequestError("Remarks are required to reject a task");

  const updated = await taskRepo.decideTaskByManager(
    taskId,
    manager.id,
    body.status,
    body.remarks?.trim(),
  );

  // Single fetch — used for both branches below
  const fullTask = await prisma.task.findUnique({
    where: { id: taskId },
    include: {
      client: { select: { userId: true, name: true, email: true } },
      service: { select: { name: true } },
    },
  });
  // emit real-time update
  emitTaskStatusUpdate(manager.organizationId, {
    taskId,
    status: updated.status,
    clientId: updated.clientId,
  });

  //  send rejection email to client
  // if (body.status === ApprovalStatus.REJECTED) {
  //   const fullTask = await prisma.task.findUnique({
  //     where: { id: taskId },
  //     include: {
  //       client: { select: { name: true, email: true } },
  //       service: { select: { name: true } },
  //     },
  //   });

  // if (body.status === ApprovalStatus.REJECTED) {
  //   if (fullTask?.client?.email) {
  //     const org = await prisma.organization.findUnique({
  //       where: { id: manager.organizationId },
  //       select: { name: true },
  //     });

  if (body.status === ApprovalStatus.REJECTED) {
    if (fullTask?.client?.email) {
      const org = await prisma.organization.findUnique({
        where: {
          id: manager.organizationId,
        },
        select: {
          name: true,
        },
      });

      await mailService.sendTaskRejectedEmail({
        to: fullTask.client.email,
        clientName: fullTask.client.name,
        taskTitle: fullTask.title,
        serviceName: fullTask.service.name,
        remarks: body.remarks?.trim(),
        rejectedAt: new Date().toLocaleDateString("en-IN", {
          day: "2-digit",
          month: "long",
          year: "numeric",
        }),
        companyName: org?.name ?? "TaxFend",
        taskUrl: `${env.FRONTEND_URL}/client/tasks`,
      });
    }

    if (fullTask?.client?.userId) {
      await notificationService.sendEmailNotification({
        userId: fullTask.client.userId,
        title: "Task Rejected",
        message: `Your ${fullTask.service.name} task, "${fullTask.title}", has been reviewed and requires further changes before it can proceed. Please review the manager's remarks, update the required information or documents, and resubmit the task for review.`,
      });
    }
  }

  if (body.status === ApprovalStatus.APPROVED && fullTask?.client?.userId) {
    await notificationService.sendEmailNotification({
      userId: fullTask.client.userId,
      title: "Task Approved",
      message: `Your ${fullTask.service.name} task, "${fullTask.title}", has been successfully reviewed and approved by the manager. The task is now ready for your review and approval. Please review the completed work and submitted documents before proceeding.`,
      actionUrl: `${env.FRONTEND_URL}/client/tasks/${taskId}`,
      actionLabel: "View Task",
    });
  }

  return updated;
}

export async function getTaskRejections(userId: string, taskId: string) {
  const client = await getClientContext(userId);

  const existing = await taskRepo.findTaskById(taskId, client.id);
  if (!existing) throw new NotFoundError("Task not found");

  const [employeeRejectedDocuments, managerRejection] = await Promise.all([
    taskRepo.findRejectedDocumentsByTask(taskId, client.id),
    taskRepo.findManagerRejection(taskId),
  ]);

  return {
    employeeRejectedDocuments,
    managerRejection: managerRejection ?? null,
  };
}

export async function getClientRejections(userId: string) {
  const client = await getClientContext(userId);
  return taskRepo.findTasksWithRejectedDocs(client.id);
}
