import { ProjectStatus } from "@capakraken/shared";
import { TRPCError } from "@trpc/server";
import { fmtEur } from "../lib/format-utils.js";
import type { TRPCContext } from "../trpc.js";

export const PROJECT_SUMMARY_SELECT = {
  id: true,
  shortCode: true,
  name: true,
  status: true,
  startDate: true,
  endDate: true,
  client: { select: { name: true } },
} as const;

export const PROJECT_SUMMARY_DETAIL_SELECT = {
  ...PROJECT_SUMMARY_SELECT,
  budgetCents: true,
  winProbability: true,
  _count: { select: { assignments: true, estimates: true } },
} as const;

export const PROJECT_IDENTIFIER_SELECT = {
  id: true,
  shortCode: true,
  name: true,
  status: true,
  startDate: true,
  endDate: true,
} as const;

export const PROJECT_DETAIL_SELECT = {
  ...PROJECT_IDENTIFIER_SELECT,
  id: true,
  shortCode: true,
  name: true,
  status: true,
  orderType: true,
  allocationType: true,
  budgetCents: true,
  winProbability: true,
  startDate: true,
  endDate: true,
  responsiblePerson: true,
  client: { select: { name: true } },
  utilizationCategory: { select: { code: true, name: true } },
  _count: { select: { assignments: true, estimates: true } },
} as const;

function formatDate(value: Date | null): string | null {
  return value ? value.toISOString().slice(0, 10) : null;
}

export function mapProjectSummary(project: {
  id: string;
  shortCode: string;
  name: string;
  status: string;
  startDate: Date | null;
  endDate: Date | null;
  client: { name: string } | null;
}) {
  return {
    id: project.id,
    code: project.shortCode,
    name: project.name,
    status: project.status,
    start: formatDate(project.startDate),
    end: formatDate(project.endDate),
    client: project.client?.name ?? null,
  };
}

export function mapProjectSummaryDetail(project: {
  id: string;
  shortCode: string;
  name: string;
  status: string;
  budgetCents: number | null;
  winProbability: number;
  startDate: Date | null;
  endDate: Date | null;
  client: { name: string } | null;
  _count: { assignments: number; estimates: number };
}) {
  return {
    id: project.id,
    code: project.shortCode,
    name: project.name,
    status: project.status,
    budget: project.budgetCents && project.budgetCents > 0 ? fmtEur(project.budgetCents) : "Not set",
    winProbability: `${project.winProbability}%`,
    start: formatDate(project.startDate),
    end: formatDate(project.endDate),
    client: project.client?.name ?? null,
    assignmentCount: project._count.assignments,
    estimateCount: project._count.estimates,
  };
}

export function mapProjectDetail(
  project: {
    id: string;
    shortCode: string;
    name: string;
    status: string;
    orderType: string;
    allocationType: string;
    budgetCents: number | null;
    winProbability: number;
    startDate: Date | null;
    endDate: Date | null;
    responsiblePerson: string | null;
    client: { name: string } | null;
    utilizationCategory: { code: string; name: string } | null;
    _count: { assignments: number; estimates: number };
  },
  topAssignments: Array<{
    resource: { displayName: string; eid: string };
    role: string | null;
    status: string;
    hoursPerDay: number;
    startDate: Date;
    endDate: Date;
  }>,
) {
  return {
    id: project.id,
    code: project.shortCode,
    name: project.name,
    status: project.status,
    orderType: project.orderType,
    allocationType: project.allocationType,
    budget: project.budgetCents && project.budgetCents > 0 ? fmtEur(project.budgetCents) : "Not set",
    budgetCents: project.budgetCents,
    winProbability: `${project.winProbability}%`,
    start: formatDate(project.startDate),
    end: formatDate(project.endDate),
    responsible: project.responsiblePerson,
    client: project.client?.name ?? null,
    category: project.utilizationCategory?.name ?? null,
    assignmentCount: project._count.assignments,
    estimateCount: project._count.estimates,
    topAllocations: topAssignments.map((assignment) => ({
      resource: assignment.resource.displayName,
      eid: assignment.resource.eid,
      role: assignment.role ?? null,
      status: assignment.status,
      hoursPerDay: assignment.hoursPerDay,
      start: formatDate(assignment.startDate),
      end: formatDate(assignment.endDate),
    })),
  };
}

export async function readProjectSummariesSnapshot(
  ctx: Pick<TRPCContext, "db">,
  input: {
    search?: string | undefined;
    status?: ProjectStatus | undefined;
    limit: number;
  },
) {
  const buildWhere = (search: string | undefined) => ({
    ...(input.status ? { status: input.status } : {}),
    ...(search
      ? {
          OR: [
            { name: { contains: search, mode: "insensitive" as const } },
            { shortCode: { contains: search, mode: "insensitive" as const } },
          ],
        }
      : {}),
  });

  let projects = await ctx.db.project.findMany({
    where: buildWhere(input.search),
    select: PROJECT_SUMMARY_SELECT,
    take: input.limit,
    orderBy: { name: "asc" },
  });

  if (projects.length === 0 && input.search) {
    const words = input.search.split(/[\s,._\-/]+/).filter((word) => word.length >= 2);
    if (words.length > 1) {
      const candidates = await ctx.db.project.findMany({
        where: {
          ...(input.status ? { status: input.status } : {}),
          OR: words.flatMap((word) => ([
            { name: { contains: word, mode: "insensitive" as const } },
            { shortCode: { contains: word, mode: "insensitive" as const } },
          ])),
        },
        select: PROJECT_SUMMARY_SELECT,
        take: input.limit * 2,
        orderBy: { name: "asc" },
      });

      projects = candidates
        .map((project) => {
          const haystack = `${project.name} ${project.shortCode}`.toLowerCase();
          const matchCount = words.filter((word) => haystack.includes(word.toLowerCase())).length;
          return { project, matchCount };
        })
        .sort((left, right) => right.matchCount - left.matchCount)
        .slice(0, input.limit)
        .map((entry) => entry.project);
    }
  }

  return {
    items: projects,
    exactMatch: input.search
      ? projects.some((project) =>
        project.name.toLowerCase().includes(input.search!.toLowerCase())
        || project.shortCode.toLowerCase().includes(input.search!.toLowerCase()))
      : true,
  };
}

export async function readProjectSummaryDetailsSnapshot(
  ctx: Pick<TRPCContext, "db">,
  input: {
    search?: string | undefined;
    status?: ProjectStatus | undefined;
    limit: number;
  },
) {
  const buildWhere = (search: string | undefined) => ({
    ...(input.status ? { status: input.status } : {}),
    ...(search
      ? {
          OR: [
            { name: { contains: search, mode: "insensitive" as const } },
            { shortCode: { contains: search, mode: "insensitive" as const } },
          ],
        }
      : {}),
  });

  let projects = await ctx.db.project.findMany({
    where: buildWhere(input.search),
    select: PROJECT_SUMMARY_DETAIL_SELECT,
    take: input.limit,
    orderBy: { name: "asc" },
  });

  if (projects.length === 0 && input.search) {
    const words = input.search.split(/[\s,._\-/]+/).filter((word) => word.length >= 2);
    if (words.length > 1) {
      const candidates = await ctx.db.project.findMany({
        where: {
          ...(input.status ? { status: input.status } : {}),
          OR: words.flatMap((word) => ([
            { name: { contains: word, mode: "insensitive" as const } },
            { shortCode: { contains: word, mode: "insensitive" as const } },
          ])),
        },
        select: PROJECT_SUMMARY_DETAIL_SELECT,
        take: input.limit * 2,
        orderBy: { name: "asc" },
      });

      projects = candidates
        .map((project) => {
          const haystack = `${project.name} ${project.shortCode}`.toLowerCase();
          const matchCount = words.filter((word) => haystack.includes(word.toLowerCase())).length;
          return { project, matchCount };
        })
        .sort((left, right) => right.matchCount - left.matchCount)
        .slice(0, input.limit)
        .map((entry) => entry.project);
    }
  }

  return {
    items: projects,
    exactMatch: input.search
      ? projects.some((project) =>
        project.name.toLowerCase().includes(input.search!.toLowerCase())
        || project.shortCode.toLowerCase().includes(input.search!.toLowerCase()))
      : true,
  };
}

export async function resolveProjectIdentifierSnapshot(
  ctx: Pick<TRPCContext, "db">,
  identifier: string,
) {
  let project = await ctx.db.project.findUnique({
    where: { id: identifier },
    select: PROJECT_IDENTIFIER_SELECT,
  });
  if (!project) {
    project = await ctx.db.project.findUnique({
      where: { shortCode: identifier },
      select: PROJECT_IDENTIFIER_SELECT,
    });
  }
  if (!project) {
    project = await ctx.db.project.findFirst({
      where: { name: { equals: identifier, mode: "insensitive" } },
      select: PROJECT_IDENTIFIER_SELECT,
    });
  }
  if (!project) {
    project = await ctx.db.project.findFirst({
      where: { name: { contains: identifier, mode: "insensitive" } },
      select: PROJECT_IDENTIFIER_SELECT,
    });
  }

  if (!project) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Project not found" });
  }

  return project;
}

export async function readProjectByIdentifierDetailSnapshot(
  ctx: Pick<TRPCContext, "db">,
  identifier: string,
) {
  const projectIdentity = await resolveProjectIdentifierSnapshot(ctx, identifier);
  const project = await ctx.db.project.findUnique({
    where: { id: projectIdentity.id },
    select: PROJECT_DETAIL_SELECT,
  });

  if (!project) {
    throw new TRPCError({ code: "NOT_FOUND", message: "Project not found" });
  }

  const topAssignments = await ctx.db.assignment.findMany({
    where: {
      projectId: project.id,
      status: { not: "CANCELLED" },
    },
    select: {
      resource: { select: { displayName: true, eid: true } },
      role: true,
      status: true,
      hoursPerDay: true,
      startDate: true,
      endDate: true,
    },
    take: 10,
    orderBy: { startDate: "desc" },
  });

  return {
    ...project,
    topAssignments,
  };
}
