import { TRPCError } from "@trpc/server";
import type { TRPCContext } from "../trpc.js";
import type { TimelineEntriesFilters } from "./timeline-read-shared.js";

type TimelineSelfServiceContext = Pick<TRPCContext, "db" | "dbUser">;

function createUtcDate(year: number, month: number, day: number): Date {
  return new Date(Date.UTC(year, month, day, 0, 0, 0, 0));
}

export function createTimelineDateRange(input: {
  startDate?: string | undefined;
  endDate?: string | undefined;
  durationDays?: number | undefined;
}): { startDate: Date; endDate: Date } {
  const now = new Date();
  const startDate = input.startDate
    ? new Date(`${input.startDate}T00:00:00.000Z`)
    : createUtcDate(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate());

  if (Number.isNaN(startDate.getTime())) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: `Invalid startDate: ${input.startDate}`,
    });
  }

  const endDate = input.endDate
    ? new Date(`${input.endDate}T00:00:00.000Z`)
    : createUtcDate(
        startDate.getUTCFullYear(),
        startDate.getUTCMonth(),
        startDate.getUTCDate() + Math.max((input.durationDays ?? 21) - 1, 0),
      );

  if (Number.isNaN(endDate.getTime())) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: `Invalid endDate: ${input.endDate}`,
    });
  }
  if (endDate < startDate) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: "endDate must be on or after startDate.",
    });
  }

  return { startDate, endDate };
}

export function normalizeTimelineStringList(values?: string[] | undefined): string[] | undefined {
  const normalized = values
    ?.map((value) => value.trim())
    .filter((value) => value.length > 0);

  return normalized && normalized.length > 0 ? normalized : undefined;
}

export function createTimelineFilters(input: {
  resourceIds?: string[] | undefined;
  projectIds?: string[] | undefined;
  clientIds?: string[] | undefined;
  chapters?: string[] | undefined;
  eids?: string[] | undefined;
  countryCodes?: string[] | undefined;
}): Omit<TimelineEntriesFilters, "startDate" | "endDate"> {
  return {
    resourceIds: normalizeTimelineStringList(input.resourceIds),
    projectIds: normalizeTimelineStringList(input.projectIds),
    clientIds: normalizeTimelineStringList(input.clientIds),
    chapters: normalizeTimelineStringList(input.chapters),
    eids: normalizeTimelineStringList(input.eids),
    countryCodes: normalizeTimelineStringList(input.countryCodes),
  };
}

async function findOwnedTimelineResourceId(
  ctx: TimelineSelfServiceContext,
): Promise<string | null> {
  if (!ctx.dbUser?.id) {
    return null;
  }

  if (!ctx.db.resource || typeof ctx.db.resource.findFirst !== "function") {
    return null;
  }

  const resource = await ctx.db.resource.findFirst({
    where: { userId: ctx.dbUser.id },
    select: { id: true },
  });

  return resource?.id ?? null;
}

export async function buildSelfServiceTimelineInput(
  ctx: TimelineSelfServiceContext,
  input: {
    startDate: Date;
    endDate: Date;
    projectIds?: string[] | undefined;
    clientIds?: string[] | undefined;
  },
): Promise<TimelineEntriesFilters | null> {
  const ownedResourceId = await findOwnedTimelineResourceId(ctx);
  if (!ownedResourceId) {
    return null;
  }

  return {
    startDate: input.startDate,
    endDate: input.endDate,
    resourceIds: [ownedResourceId],
    projectIds: normalizeTimelineStringList(input.projectIds),
    clientIds: normalizeTimelineStringList(input.clientIds),
  };
}

export function buildTimelineEntriesDetailInput(input: {
  startDate?: string | undefined;
  endDate?: string | undefined;
  durationDays?: number | undefined;
  resourceIds?: string[] | undefined;
  projectIds?: string[] | undefined;
  clientIds?: string[] | undefined;
  chapters?: string[] | undefined;
  eids?: string[] | undefined;
  countryCodes?: string[] | undefined;
}) {
  const period = createTimelineDateRange(input);
  const filters = createTimelineFilters(input);

  return {
    period,
    filters,
    timelineInput: {
      ...filters,
      startDate: period.startDate,
      endDate: period.endDate,
    },
  };
}
