import { TRPCError } from "@trpc/server";
import { z } from "zod";
import { COLUMN_MAP, type ColumnDef, RESOURCE_MONTH_COLUMNS } from "./report-columns.js";
import type { ReportExplainability } from "./report-explainability.js";

export const ENTITY_MAP = {
  resource: "resource",
  project: "project",
  assignment: "assignment",
  resource_month: "resource_month",
} as const;

export type EntityKey = keyof typeof ENTITY_MAP;

const PERIOD_MONTH_PATTERN = /^\d{4}-(0[1-9]|1[0-2])$/;

const ALLOWED_SCALAR_FIELDS: Record<EntityKey, Set<string>> = {
  resource: new Set([
    "id", "eid", "displayName", "email", "chapter", "resourceType",
    "lcrCents", "ucrCents", "currency", "chargeabilityTarget", "fte",
    "enterpriseId", "portfolioUrl", "valueScore", "valueScoreUpdatedAt",
    "isActive", "chgResponsibility", "rolledOff", "departed",
    "postalCode", "federalState", "createdAt", "updatedAt",
  ]),
  project: new Set([
    "id", "shortCode", "name", "orderType", "allocationType", "status",
    "winProbability", "budgetCents", "shoringThreshold", "onshoreCountryCode", "color",
    "startDate", "endDate",
    "responsiblePerson", "createdAt", "updatedAt",
  ]),
  assignment: new Set([
    "id", "startDate", "endDate", "hoursPerDay", "percentage",
    "role", "dailyCostCents", "status", "createdAt", "updatedAt",
  ]),
  resource_month: new Set(RESOURCE_MONTH_COLUMNS.map((column) => column.key)),
};

export function getValidScalarField(entity: EntityKey, field: string): string | null {
  if (ALLOWED_SCALAR_FIELDS[entity].has(field)) {
    return field;
  }
  return null;
}

export function getColumnDef(entity: EntityKey, columnKey: string): ColumnDef | undefined {
  return COLUMN_MAP[entity].find((column) => column.key === columnKey);
}

function assertKnownColumns(entity: EntityKey, columns: string[]): void {
  const invalidColumns = columns.filter((column) => !getColumnDef(entity, column));
  if (invalidColumns.length > 0) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: `Unknown columns for ${entity}: ${invalidColumns.join(", ")}`,
    });
  }
}

function assertValidFilterField(entity: EntityKey, field: string): string {
  if (entity === "resource_month") {
    if (!getColumnDef(entity, field)) {
      throw new TRPCError({
        code: "BAD_REQUEST",
        message: `Unknown filter field for ${entity}: ${field}`,
      });
    }
    return field;
  }

  const validField = getValidScalarField(entity, field);
  if (!validField) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: `Unsupported filter field for ${entity}: ${field}`,
    });
  }
  return validField;
}

function assertValidSortField(entity: EntityKey, field: string): void {
  if (entity === "resource_month") {
    if (!getColumnDef(entity, field)) {
      throw new TRPCError({
        code: "BAD_REQUEST",
        message: `Unknown sort field for ${entity}: ${field}`,
      });
    }
    return;
  }

  if (!getValidScalarField(entity, field)) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: `Unsupported sort field for ${entity}: ${field}`,
    });
  }
}

function assertValidGroupField(entity: EntityKey, field: string): void {
  const knownField =
    entity === "resource_month"
      ? getColumnDef(entity, field)?.key
      : getValidScalarField(entity, field);
  if (!knownField) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: `Unsupported group field for ${entity}: ${field}`,
    });
  }
}

function parseFilterValueOrThrow(def: ColumnDef, value: string): unknown {
  if (def.dataType === "number") {
    const parsed = Number(value);
    if (Number.isNaN(parsed)) {
      throw new TRPCError({
        code: "BAD_REQUEST",
        message: `Invalid numeric filter value for ${def.key}: ${value}`,
      });
    }
    return parsed;
  }

  if (def.dataType === "boolean") {
    if (value !== "true" && value !== "false") {
      throw new TRPCError({
        code: "BAD_REQUEST",
        message: `Invalid boolean filter value for ${def.key}: ${value}`,
      });
    }
    return value === "true";
  }

  if (def.dataType === "date") {
    const parsed = new Date(value);
    if (Number.isNaN(parsed.getTime())) {
      throw new TRPCError({
        code: "BAD_REQUEST",
        message: `Invalid date filter value for ${def.key}: ${value}`,
      });
    }
    return parsed;
  }

  return value;
}

export const reportEntitySchema = z.enum(["resource", "project", "assignment", "resource_month"]);

export const FilterSchema = z.object({
  field: z.string().min(1),
  op: z.enum(["eq", "neq", "gt", "lt", "gte", "lte", "contains", "in"]),
  value: z.string(),
});

export const ReportInputSchema = z.object({
  entity: reportEntitySchema,
  columns: z.array(z.string()).min(1),
  filters: z.array(FilterSchema).default([]),
  groupBy: z.string().optional(),
  sortBy: z.string().optional(),
  sortDir: z.enum(["asc", "desc"]).default("asc"),
  periodMonth: z.string().regex(PERIOD_MONTH_PATTERN).optional(),
  limit: z.number().int().min(1).max(5000).default(50),
  offset: z.number().int().min(0).default(0),
});

export const ReportTemplateConfigSchema = ReportInputSchema.omit({ limit: true, offset: true });

export type ReportInput = z.infer<typeof ReportInputSchema>;
export type FilterInput = z.infer<typeof FilterSchema>;

export interface ReportGroupSummary {
  key: string;
  label: string;
  rowCount: number;
  startIndex: number;
}

export interface ReportQueryResult {
  rows: Record<string, unknown>[];
  columns: string[];
  totalCount: number;
  groups: ReportGroupSummary[];
  explainability?: ReportExplainability;
}

export function validateReportInput(input: ReportInput | z.infer<typeof ReportTemplateConfigSchema>): void {
  assertKnownColumns(input.entity, input.columns);

  if (input.periodMonth && !PERIOD_MONTH_PATTERN.test(input.periodMonth)) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: `Invalid periodMonth: ${input.periodMonth}. Expected YYYY-MM with a month between 01 and 12.`,
    });
  }

  if (input.entity !== "resource_month" && input.periodMonth) {
    throw new TRPCError({
      code: "BAD_REQUEST",
      message: "periodMonth is only supported for resource_month reports",
    });
  }

  if (input.sortBy) {
    assertValidSortField(input.entity, input.sortBy);
  }

  if (input.groupBy) {
    assertValidGroupField(input.entity, input.groupBy);
  }

  for (const filter of input.filters) {
    const field = assertValidFilterField(input.entity, filter.field);
    const def = getColumnDef(input.entity, field);
    if (!def) {
      throw new TRPCError({
        code: "BAD_REQUEST",
        message: `Unknown filter field for ${input.entity}: ${filter.field}`,
      });
    }

    if (filter.op === "contains" || filter.op === "in") {
      if (def.dataType !== "string") {
        throw new TRPCError({
          code: "BAD_REQUEST",
          message: `Filter operator ${filter.op} is only supported for string fields like ${def.key}`,
        });
      }
      continue;
    }

    void parseFilterValueOrThrow(def, filter.value);
  }
}

export function buildSelect(entity: EntityKey, columns: string[]): Record<string, unknown> {
  const entityColumns = COLUMN_MAP[entity];
  const select: Record<string, unknown> = { id: true };

  for (const colKey of columns) {
    const def = entityColumns.find((column) => column.key === colKey);
    if (!def) {
      continue;
    }

    if (colKey.includes(".")) {
      const relationName = def.prismaPath ?? colKey.split(".")[0]!;
      const existing = select[relationName];
      const fieldSegments = colKey.split(".").slice(1);
      const relationSelect = existing && typeof existing === "object" && existing !== null && "select" in existing
        ? (existing as { select: Record<string, unknown> }).select
        : {};
      mergeSelectPath(relationSelect, fieldSegments);
      select[relationName] = { select: relationSelect };
    } else {
      select[colKey] = true;
    }
  }

  return select;
}

function mergeSelectPath(target: Record<string, unknown>, segments: string[]): void {
  const [head, ...tail] = segments;
  if (!head) {
    return;
  }

  if (tail.length === 0) {
    target[head] = true;
    return;
  }

  const existing = target[head];
  const nestedSelect = existing && typeof existing === "object" && existing !== null && "select" in existing
    ? (existing as { select: Record<string, unknown> }).select
    : {};

  mergeSelectPath(nestedSelect, tail);
  target[head] = { select: nestedSelect };
}

export function buildWhere(
  entity: EntityKey,
  filters: Array<{ field: string; op: string; value: string }>,
): Record<string, unknown> {
  const where: Record<string, unknown> = {};

  for (const filter of filters) {
    const field = assertValidFilterField(entity, filter.field);
    const colDef = getColumnDef(entity, field);
    if (!colDef) {
      throw new TRPCError({
        code: "BAD_REQUEST",
        message: `Unknown filter field for ${entity}: ${filter.field}`,
      });
    }
    const parsedValue = parseFilterValueOrThrow(colDef, filter.value);

    switch (filter.op) {
      case "eq":
        where[field] = parsedValue;
        break;
      case "neq":
        where[field] = { not: parsedValue };
        break;
      case "gt":
        where[field] = { gt: parsedValue };
        break;
      case "lt":
        where[field] = { lt: parsedValue };
        break;
      case "gte":
        where[field] = { gte: parsedValue };
        break;
      case "lte":
        where[field] = { lte: parsedValue };
        break;
      case "contains":
        if (colDef.dataType !== "string") {
          throw new TRPCError({
            code: "BAD_REQUEST",
            message: `Filter operator contains is only supported for string fields like ${field}`,
          });
        }
        where[field] = { contains: filter.value, mode: "insensitive" };
        break;
      case "in":
        if (colDef.dataType !== "string") {
          throw new TRPCError({
            code: "BAD_REQUEST",
            message: `Filter operator in is only supported for string fields like ${field}`,
          });
        }
        where[field] = { in: filter.value.split(",").map((value) => value.trim()) };
        break;
      default:
        throw new TRPCError({
          code: "BAD_REQUEST",
          message: `Unsupported filter operator: ${filter.op}`,
        });
    }
  }

  return where;
}

export function flattenRow(row: Record<string, unknown>, prefix = ""): Record<string, unknown> {
  const result: Record<string, unknown> = {};
  for (const [key, value] of Object.entries(row)) {
    const fullKey = prefix ? `${prefix}.${key}` : key;
    if (value !== null && typeof value === "object" && !(value instanceof Date) && !Array.isArray(value)) {
      Object.assign(result, flattenRow(value as Record<string, unknown>, fullKey));
    } else {
      result[fullKey] = value;
    }
  }
  return result;
}

export function csvEscape(value: unknown): string {
  if (value === null || value === undefined) {
    return "";
  }
  if (value instanceof Date) {
    return value.toISOString();
  }
  const str = String(value);
  if (str.includes(",") || str.includes('"') || str.includes("\n")) {
    return `"${str.replace(/"/g, '""')}"`;
  }
  return str;
}
