import {
  CaptureAutomationRunStatus,
  CaptureMode,
  DepartmentKey as PrismaDepartmentKey,
  ProjectCaptureEvidenceKind,
  WeeklyKpiStatus,
  type Prisma,
} from "@prisma/client";
import { prisma } from "@/lib/prisma";
import { getDepartmentLabel } from "@/lib/departments";
import { getWeekBounds } from "@/lib/kpis/date";
import { ensureWeeklyKpiSnapshotForWeek } from "@/lib/kpis/persistence";
import { deriveKpiStatus, parseDueDateIso, parseNumber, parsePercent } from "@/lib/kpis/parsing";
import type {
  CaptureAssigneeDTO,
  CaptureAutomationRunDTO,
  CaptureBoardFilters,
  CaptureBoardResponse,
  CaptureEvidenceDTO,
  CaptureModeDTO,
  CaptureRowDTO,
  CaptureViewerContext,
  OwnerCaptureRollupDTO,
} from "@/lib/capture/types";
import { deriveCaptureStatus } from "@/lib/capture/status";

const ACTIVE_CAPTURE_DEPARTMENTS: PrismaDepartmentKey[] = [
  PrismaDepartmentKey.marketing,
  PrismaDepartmentKey.capital_humano,
  PrismaDepartmentKey.operaciones,
  PrismaDepartmentKey.proyectos,
];

function toIsoDate(value: Date | null): string | null {
  if (!value) {
    return null;
  }

  return value.toISOString().slice(0, 10);
}

function toIsoDateTime(value: Date | null): string | null {
  if (!value) {
    return null;
  }

  return value.toISOString();
}

function normalizeDepartment(department: PrismaDepartmentKey | null | undefined): PrismaDepartmentKey | null {
  if (!department) {
    return null;
  }

  if (department === PrismaDepartmentKey.administracion) {
    return PrismaDepartmentKey.capital_humano;
  }

  return department;
}

function normalizeTextForDedup(value: string | null | undefined): string {
  if (!value) {
    return "";
  }

  return value
    .normalize("NFD")
    .replace(/[\u0300-\u036f]/g, "")
    .toLowerCase()
    .replace(/\s+/g, " ")
    .trim();
}

function canonicalMappedDepartment(
  rawSectionLabel: string,
  mappedDepartment: PrismaDepartmentKey | null | undefined
): PrismaDepartmentKey | null {
  const normalized = rawSectionLabel.toUpperCase();

  if (normalized.includes("ADMINISTRACION") || normalized.includes("PRIMITIVO V.")) {
    return PrismaDepartmentKey.capital_humano;
  }

  if (
    normalized.includes("OOH / COMUNIDAD") ||
    normalized.includes("COMMUNITY SQUAD") ||
    normalized.includes("CHAMPIONS")
  ) {
    return PrismaDepartmentKey.proyectos;
  }

  return normalizeDepartment(mappedDepartment ?? null);
}

function isActiveCaptureDepartment(department: PrismaDepartmentKey | null | undefined): department is PrismaDepartmentKey {
  return Boolean(department && ACTIVE_CAPTURE_DEPARTMENTS.includes(department));
}

function resolveViewerDepartment(viewer: CaptureViewerContext): PrismaDepartmentKey | null {
  const normalized = normalizeDepartment((viewer.department as PrismaDepartmentKey | null | undefined) ?? null);
  return isActiveCaptureDepartment(normalized) ? normalized : null;
}

function resolveScopedDepartment(
  viewer: CaptureViewerContext,
  requestedDepartment: string | null | undefined
): PrismaDepartmentKey | null {
  const requested = normalizeDepartment((requestedDepartment as PrismaDepartmentKey | null | undefined) ?? null);
  const viewerDepartment = resolveViewerDepartment(viewer);

  if (viewer.role === "owner") {
    if (requested && isActiveCaptureDepartment(requested)) {
      return requested;
    }

    return PrismaDepartmentKey.marketing;
  }

  if (viewer.role === "leader" || viewer.role === "employee") {
    if (!viewerDepartment) {
      return null;
    }

    if (requested && requested !== viewerDepartment) {
      return null;
    }

    return viewerDepartment;
  }

  return null;
}

function canManageDepartment(viewer: CaptureViewerContext, department: PrismaDepartmentKey): boolean {
  if (viewer.role === "owner") {
    return true;
  }

  return viewer.role === "leader" && resolveViewerDepartment(viewer) === department;
}

function canEditCatalogEntry(
  viewer: CaptureViewerContext,
  input: { department: PrismaDepartmentKey; ownerUserId: string | null }
): boolean {
  if (canManageDepartment(viewer, input.department)) {
    return true;
  }

  if (viewer.role !== "employee") {
    return false;
  }

  return resolveViewerDepartment(viewer) === input.department && Boolean(input.ownerUserId && input.ownerUserId === viewer.userId);
}

function defaultCaptureMode(sectionKey: string): CaptureMode {
  if (sectionKey.endsWith("-auto-v1")) {
    return CaptureMode.auto;
  }

  return CaptureMode.manual;
}

function defaultFreshnessSlaHours(mode: CaptureMode): number {
  if (mode === CaptureMode.auto) {
    return 48;
  }

  if (mode === CaptureMode.hybrid) {
    return 72;
  }

  return 168;
}

function toCaptureModeDTO(value: CaptureMode): CaptureModeDTO {
  if (value === CaptureMode.auto || value === CaptureMode.hybrid) {
    return value;
  }

  return "manual";
}

function toPrismaStatus(value: ReturnType<typeof deriveKpiStatus>): WeeklyKpiStatus {
  if (value === "on_track") {
    return WeeklyKpiStatus.on_track;
  }

  if (value === "watch") {
    return WeeklyKpiStatus.watch;
  }

  if (value === "risk") {
    return WeeklyKpiStatus.risk;
  }

  return WeeklyKpiStatus.no_score;
}

function toKpiStatusDTO(value: WeeklyKpiStatus): CaptureRowDTO["kpiStatus"] {
  if (value === WeeklyKpiStatus.on_track || value === WeeklyKpiStatus.watch || value === WeeklyKpiStatus.risk) {
    return value;
  }

  return "no_score";
}

function parseIsoDateInput(value: string | undefined): Date | null {
  if (!value) {
    return null;
  }

  const parsed = new Date(value);
  if (Number.isNaN(parsed.getTime())) {
    return null;
  }

  return parsed;
}

export async function ensureCaptureCatalogForWeek(weekStart: string): Promise<void> {
  await ensureWeeklyKpiSnapshotForWeek(weekStart);
  const weekStartDate = new Date(`${weekStart}T00:00:00.000Z`);

  const rows = await prisma.weeklyKpiRow.findMany({
    where: {
      section: {
        snapshot: {
          weekStart: weekStartDate,
        },
      },
    },
    include: {
      section: {
        include: {
          snapshot: {
            select: {
              weekStart: true,
            },
          },
        },
      },
    },
  });

  for (const row of rows) {
    const mappedDepartment = canonicalMappedDepartment(row.section.rawSectionLabel, row.section.mappedDepartment);
    if (!isActiveCaptureDepartment(mappedDepartment)) {
      continue;
    }

    const mode = defaultCaptureMode(row.section.sectionKey);
    await prisma.kpiCaptureCatalog.upsert({
      where: {
        weekStart_sectionKey_rowKey: {
          weekStart: weekStartDate,
          sectionKey: row.section.sectionKey,
          rowKey: row.rowKey,
        },
      },
      create: {
        weekStart: weekStartDate,
        sectionKey: row.section.sectionKey,
        rowKey: row.rowKey,
        weeklyKpiRowId: row.id,
        department: mappedDepartment,
        captureMode: mode,
        automationSource: mode === CaptureMode.auto ? `${mappedDepartment}-auto` : null,
        freshnessSlaHours: defaultFreshnessSlaHours(mode),
      },
      update: {
        weeklyKpiRowId: row.id,
        department: mappedDepartment,
      },
    });
  }
}

function buildEvidenceWhere(
  department: PrismaDepartmentKey,
  weekStartDate: Date,
  filters?: CaptureBoardFilters
): Prisma.KpiCaptureEvidenceWhereInput {
  const from = parseIsoDateInput(filters?.from);
  const to = parseIsoDateInput(filters?.to);
  const query = filters?.query?.trim();

  return {
    department,
    weekStart: weekStartDate,
    ...(filters?.initiativeId ? { initiativeId: filters.initiativeId } : {}),
    ...(filters?.taskId ? { taskId: filters.taskId } : {}),
    ...(filters?.uploadedById ? { uploadedById: filters.uploadedById } : {}),
    ...(from || to
      ? {
          createdAt: {
            ...(from ? { gte: from } : {}),
            ...(to ? { lte: to } : {}),
          },
        }
      : {}),
    ...(query
      ? {
          OR: [
            {
              title: {
                contains: query,
                mode: "insensitive",
              },
            },
            {
              note: {
                contains: query,
                mode: "insensitive",
              },
            },
          ],
        }
      : {}),
  };
}

function mapCaptureEvidence(record: {
  id: string;
  department: PrismaDepartmentKey;
  weekStart: Date;
  sectionKey: string;
  rowKey: string;
  weeklyKpiRowId: string | null;
  initiativeId: string | null;
  taskId: string | null;
  uploadedById: string;
  kind: ProjectCaptureEvidenceKind;
  title: string;
  note: string | null;
  url: string | null;
  storagePath: string | null;
  mimeType: string | null;
  sizeBytes: number | null;
  createdAt: Date;
  updatedAt: Date;
  uploadedBy: {
    name: string | null;
    email: string;
  };
  initiative?: {
    name: string;
  } | null;
  task?: {
    title: string;
  } | null;
}): CaptureEvidenceDTO {
  const kind = record.kind === "photo" || record.kind === "link" ? record.kind : "document";
  return {
    id: record.id,
    department: record.department as CaptureEvidenceDTO["department"],
    weekStart: record.weekStart.toISOString().slice(0, 10),
    sectionKey: record.sectionKey,
    rowKey: record.rowKey,
    rowId: record.weeklyKpiRowId,
    initiativeId: record.initiativeId,
    initiativeName: record.initiative?.name ?? null,
    taskId: record.taskId,
    taskTitle: record.task?.title ?? null,
    uploadedById: record.uploadedById,
    uploaderName: record.uploadedBy.name?.trim() || record.uploadedBy.email,
    kind,
    title: record.title,
    note: record.note,
    url: record.url,
    hasFile: Boolean(record.storagePath),
    mimeType: record.mimeType,
    sizeBytes: record.sizeBytes,
    createdAt: record.createdAt.toISOString(),
    updatedAt: record.updatedAt.toISOString(),
  };
}

function mapAutomationRun(run: {
  id: string;
  department: PrismaDepartmentKey;
  weekStart: Date;
  source: string;
  status: CaptureAutomationRunStatus;
  rowsTouched: number;
  errorMessage: string | null;
  triggeredById: string | null;
  startedAt: Date;
  completedAt: Date | null;
  triggeredBy: {
    name: string | null;
    email: string;
  } | null;
}): CaptureAutomationRunDTO {
  return {
    id: run.id,
    department: run.department as CaptureAutomationRunDTO["department"],
    weekStart: run.weekStart.toISOString().slice(0, 10),
    source: run.source,
    status: run.status,
    rowsTouched: run.rowsTouched,
    errorMessage: run.errorMessage,
    triggeredById: run.triggeredById,
    triggeredByName: run.triggeredBy?.name?.trim() || run.triggeredBy?.email || null,
    startedAt: run.startedAt.toISOString(),
    completedAt: toIsoDateTime(run.completedAt),
  };
}

function dedupeCaptureRows(rows: CaptureRowDTO[]): CaptureRowDTO[] {
  const deduped = new Map<string, CaptureRowDTO>();
  for (const row of rows) {
    const dedupKey = `${normalizeTextForDedup(row.sectionLabel)}|${normalizeTextForDedup(row.responsibility)}`;
    const existing = deduped.get(dedupKey);
    if (!existing) {
      deduped.set(dedupKey, row);
      continue;
    }

    const existingScore = (existing.evidenceCount ?? 0) + (existing.compliancePct ?? 0);
    const rowScore = (row.evidenceCount ?? 0) + (row.compliancePct ?? 0);
    deduped.set(dedupKey, rowScore > existingScore ? row : existing);
  }

  return Array.from(deduped.values()).sort((a, b) => a.sectionKey.localeCompare(b.sectionKey, "es") || a.rowKey.localeCompare(b.rowKey, "es"));
}

export async function getCaptureBoard(input: {
  viewer: CaptureViewerContext;
  weekStartInput?: string | null;
  requestedDepartment?: string | null;
  filters?: CaptureBoardFilters;
  ensureCatalog?: boolean;
}): Promise<CaptureBoardResponse> {
  const { weekStart, weekEnd } = getWeekBounds(input.weekStartInput);
  if (input.ensureCatalog ?? true) {
    await ensureCaptureCatalogForWeek(weekStart);
  }

  const department = resolveScopedDepartment(input.viewer, input.requestedDepartment ?? null);
  if (!department) {
    throw new Error("Forbidden");
  }

  const weekStartDate = new Date(`${weekStart}T00:00:00.000Z`);

  const [rowsRaw, evidenceRaw, evidenceAgg, runsRaw] = await Promise.all([
    prisma.weeklyKpiRow.findMany({
      where: {
        section: {
          snapshot: {
            weekStart: weekStartDate,
          },
        },
      },
      include: {
        section: {
          include: {
            snapshot: true,
          },
        },
        captureCatalog: {
          include: {
            ownerUser: {
              select: {
                id: true,
                name: true,
                email: true,
              },
            },
            lastCapturedBy: {
              select: {
                id: true,
                name: true,
                email: true,
              },
            },
          },
        },
      },
      orderBy: [{ section: { sortOrder: "asc" } }, { sortOrder: "asc" }],
    }),
    prisma.kpiCaptureEvidence.findMany({
      where: buildEvidenceWhere(department, weekStartDate, input.filters),
      include: {
        uploadedBy: {
          select: {
            name: true,
            email: true,
          },
        },
        initiative: {
          select: {
            name: true,
          },
        },
        task: {
          select: {
            title: true,
          },
        },
      },
      orderBy: [{ createdAt: "desc" }],
      take: 400,
    }),
    prisma.kpiCaptureEvidence.groupBy({
      by: ["weeklyKpiRowId"],
      where: {
        department,
        weekStart: weekStartDate,
      },
      _count: {
        _all: true,
      },
      _max: {
        createdAt: true,
      },
    }),
    prisma.kpiCaptureAutomationRun.findMany({
      where: {
        department,
        weekStart: weekStartDate,
      },
      include: {
        triggeredBy: {
          select: {
            name: true,
            email: true,
          },
        },
      },
      orderBy: [{ startedAt: "desc" }],
      take: 40,
    }),
  ]);

  const evidenceAggByRowId = new Map<string, { count: number; latestAt: string | null }>();
  for (const entry of evidenceAgg) {
    if (!entry.weeklyKpiRowId) {
      continue;
    }

    evidenceAggByRowId.set(entry.weeklyKpiRowId, {
      count: entry._count._all,
      latestAt: toIsoDateTime(entry._max.createdAt ?? null),
    });
  }

  const runsBySource = new Map<string, CaptureAutomationRunDTO[]>();
  const runs = runsRaw.map(mapAutomationRun);
  for (const run of runs) {
    const list = runsBySource.get(run.source) ?? [];
    list.push(run);
    runsBySource.set(run.source, list);
  }

  const rows: CaptureRowDTO[] = [];
  for (const row of rowsRaw) {
    const mappedDepartment = canonicalMappedDepartment(row.section.rawSectionLabel, row.section.mappedDepartment);
    if (mappedDepartment !== department) {
      continue;
    }

    const catalog = row.captureCatalog;
    const mode = catalog?.captureMode ?? defaultCaptureMode(row.section.sectionKey);
    const automationSource = catalog?.automationSource ?? null;
    const runList = automationSource ? runsBySource.get(automationSource) ?? [] : runs;
    const latestRun = runList[0] ?? null;
    const latestSuccessRun = runList.find((run) => run.status === "success") ?? null;

    const evidenceAggItem = evidenceAggByRowId.get(row.id);
    const status = deriveCaptureStatus({
      captureMode: toCaptureModeDTO(mode),
      freshnessSlaHours: catalog?.freshnessSlaHours ?? defaultFreshnessSlaHours(mode),
      objectiveIndicator: row.objectiveIndicatorText,
      quantityQuality: row.quantityQualityText,
      compliance: row.complianceText,
      compliancePct: row.compliancePct,
      dueCommitment: row.dueCommitmentText,
      lastCapturedAt: toIsoDateTime(catalog?.lastCapturedAt ?? row.lastCapturedAt),
      lastAutomationAt: toIsoDateTime(catalog?.lastAutomationAt ?? row.lastAutomationAt),
      lastEvidenceAt: evidenceAggItem?.latestAt ?? null,
      latestRunStatus: latestRun?.status ?? null,
      latestRunAt: latestRun?.startedAt ?? null,
      latestSuccessfulRunAt: latestSuccessRun?.completedAt ?? latestSuccessRun?.startedAt ?? null,
    });

    const ownerUserName =
      catalog?.ownerUser?.name?.trim() || catalog?.ownerUser?.email || null;
    const lastCapturedByName =
      catalog?.lastCapturedBy?.name?.trim() || catalog?.lastCapturedBy?.email || null;

    rows.push({
      id: row.id,
      weekStart,
      weekEnd,
      department: mappedDepartment as CaptureRowDTO["department"],
      sectionKey: row.section.sectionKey,
      sectionLabel: row.section.rawSectionLabel,
      rowKey: row.rowKey,
      responsibility: row.responsibilityText,
      ownerTeamLabel: row.section.ownerTeamLabel,
      objectiveIndicator: row.objectiveIndicatorText,
      quantityQuality: row.quantityQualityText,
      compliance: row.complianceText,
      compliancePct: row.compliancePct,
      dueCommitment: row.dueCommitmentText,
      dueDate: toIsoDate(row.dueDate),
      kpiStatus: toKpiStatusDTO(row.status),
      captureMode: toCaptureModeDTO(mode),
      automationSource,
      ownerUserId: catalog?.ownerUserId ?? null,
      ownerUserName,
      freshnessSlaHours: catalog?.freshnessSlaHours ?? defaultFreshnessSlaHours(mode),
      captureNote: catalog?.captureNote ?? row.captureNote ?? null,
      lastCapturedAt: toIsoDateTime(catalog?.lastCapturedAt ?? row.lastCapturedAt),
      lastCapturedById: catalog?.lastCapturedById ?? row.lastCapturedById,
      lastCapturedByName,
      lastAutomationAt: toIsoDateTime(catalog?.lastAutomationAt ?? row.lastAutomationAt),
      lastEvidenceAt: evidenceAggItem?.latestAt ?? null,
      evidenceCount: evidenceAggItem?.count ?? 0,
      status,
      canEdit: canEditCatalogEntry(input.viewer, {
        department,
        ownerUserId: catalog?.ownerUserId ?? null,
      }),
      canEditCore: input.viewer.role === "owner",
    });
  }

  const dedupedRows = dedupeCaptureRows(rows);

  const summary = {
    totalRows: dedupedRows.length,
    capturedRows: dedupedRows.filter((row) => row.status === "captured").length,
    pendingRows: dedupedRows.filter((row) => row.status === "pending").length,
    inProgressRows: dedupedRows.filter((row) => row.status === "in_progress").length,
    staleRows: dedupedRows.filter((row) => row.status === "stale").length,
    errorRows: dedupedRows.filter((row) => row.status === "error").length,
    atRiskRows: dedupedRows.filter((row) => row.kpiStatus === "risk").length,
    capturePct: 0,
  };

  summary.capturePct =
    summary.totalRows > 0
      ? Math.round((summary.capturedRows / summary.totalRows) * 1000) / 10
      : 0;

  return {
    weekStart,
    weekEnd,
    department: department as CaptureBoardResponse["department"],
    summary,
    rows: dedupedRows,
    evidence: evidenceRaw.map(mapCaptureEvidence),
    automationRuns: runs,
  };
}

async function ensureCatalogForRow(input: {
  row: {
    id: string;
    rowKey: string;
    section: {
      sectionKey: string;
      rawSectionLabel: string;
      mappedDepartment: PrismaDepartmentKey | null;
      snapshot: {
        weekStart: Date;
      };
    };
  };
}): Promise<{
  id: string;
  weekStart: Date;
  sectionKey: string;
  rowKey: string;
  department: PrismaDepartmentKey;
  ownerUserId: string | null;
  captureMode: CaptureMode;
  automationSource: string | null;
  freshnessSlaHours: number;
}> {
  const department = canonicalMappedDepartment(input.row.section.rawSectionLabel, input.row.section.mappedDepartment);
  if (!isActiveCaptureDepartment(department)) {
    throw new Error("Row department is not in capture scope.");
  }

  const mode = defaultCaptureMode(input.row.section.sectionKey);
  return prisma.kpiCaptureCatalog.upsert({
    where: {
      weekStart_sectionKey_rowKey: {
        weekStart: input.row.section.snapshot.weekStart,
        sectionKey: input.row.section.sectionKey,
        rowKey: input.row.rowKey,
      },
    },
    create: {
      weekStart: input.row.section.snapshot.weekStart,
      sectionKey: input.row.section.sectionKey,
      rowKey: input.row.rowKey,
      weeklyKpiRowId: input.row.id,
      department,
      captureMode: mode,
      automationSource: mode === CaptureMode.auto ? `${department}-auto` : null,
      freshnessSlaHours: defaultFreshnessSlaHours(mode),
    },
    update: {
      weeklyKpiRowId: input.row.id,
      department,
    },
    select: {
      id: true,
      weekStart: true,
      sectionKey: true,
      rowKey: true,
      department: true,
      ownerUserId: true,
      captureMode: true,
      automationSource: true,
      freshnessSlaHours: true,
    },
  });
}

export async function patchCaptureRow(input: {
  rowId: string;
  viewer: CaptureViewerContext;
  payload: {
    objectiveIndicator?: string | null;
    quantityQuality?: string | null;
    compliance?: string | null;
    dueCommitment?: string | null;
    captureNote?: string | null;
    ownerUserId?: string | null;
    captureMode?: CaptureModeDTO;
    automationSource?: string | null;
    freshnessSlaHours?: number;
  };
}) {
  const row = await prisma.weeklyKpiRow.findUnique({
    where: { id: input.rowId },
    include: {
      section: {
        include: {
          snapshot: true,
        },
      },
    },
  });

  if (!row) {
    throw new Error("KPI row not found.");
  }

  const catalog = await ensureCatalogForRow({ row });
  const department = catalog.department;

  const scopedDepartment = resolveScopedDepartment(input.viewer, department);
  if (!scopedDepartment || scopedDepartment !== department) {
    throw new Error("Forbidden");
  }

  if (!canEditCatalogEntry(input.viewer, { department, ownerUserId: catalog.ownerUserId })) {
    throw new Error("Forbidden");
  }

  const hasManageFields =
    input.payload.ownerUserId !== undefined ||
    input.payload.captureMode !== undefined ||
    input.payload.automationSource !== undefined ||
    input.payload.freshnessSlaHours !== undefined;

  const canManage = canManageDepartment(input.viewer, department);
  if (hasManageFields && !canManage) {
    throw new Error("Forbidden");
  }

  const nextObjectiveIndicator =
    input.payload.objectiveIndicator !== undefined
      ? input.payload.objectiveIndicator?.trim() || null
      : row.objectiveIndicatorText;
  const isCoreEditAttempt =
    input.payload.objectiveIndicator !== undefined && nextObjectiveIndicator !== row.objectiveIndicatorText;
  if (isCoreEditAttempt && input.viewer.role !== "owner") {
    throw new Error("Forbidden");
  }

  if (input.payload.ownerUserId !== undefined && input.payload.ownerUserId !== null) {
    const ownerUser = await prisma.user.findUnique({
      where: { id: input.payload.ownerUserId },
      select: {
        id: true,
        department: true,
      },
    });

    const ownerDepartment = normalizeDepartment(ownerUser?.department ?? null);
    if (!ownerUser || ownerDepartment !== department) {
      throw new Error("Owner must belong to the same department.");
    }
  }

  const objectiveIndicator = nextObjectiveIndicator;
  const quantityQuality =
    input.payload.quantityQuality !== undefined
      ? input.payload.quantityQuality?.trim() || null
      : row.quantityQualityText;
  const compliance =
    input.payload.compliance !== undefined
      ? input.payload.compliance?.trim() || null
      : row.complianceText;
  const dueCommitment =
    input.payload.dueCommitment !== undefined
      ? input.payload.dueCommitment?.trim() || null
      : row.dueCommitmentText;

  const weekStart = row.section.snapshot.weekStart.toISOString().slice(0, 10);
  const targetValue = parseNumber(objectiveIndicator);
  const quantityValue = parseNumber(quantityQuality);
  const compliancePct =
    parsePercent(compliance) ?? parsePercent(quantityQuality) ?? parsePercent(objectiveIndicator);
  const dueDateIso =
    parseDueDateIso(dueCommitment, weekStart) ?? parseDueDateIso(objectiveIndicator, weekStart);
  const status = toPrismaStatus(
    deriveKpiStatus({
      compliancePct,
      quantityValue,
      objectiveIndicatorText: objectiveIndicator,
      dueDateIso,
      todayIso: weekStart,
    })
  );

  const now = new Date();
  await prisma.$transaction(async (tx) => {
    await tx.weeklyKpiRow.update({
      where: { id: row.id },
      data: {
        objectiveIndicatorText: objectiveIndicator,
        quantityQualityText: quantityQuality,
        complianceText: compliance,
        dueCommitmentText: dueCommitment,
        targetValue,
        quantityValue,
        compliancePct,
        dueDate: dueDateIso ? new Date(`${dueDateIso}T00:00:00.000Z`) : null,
        status,
        captureNote:
          input.payload.captureNote !== undefined
            ? input.payload.captureNote?.trim() || null
            : row.captureNote,
        lastCapturedAt: now,
        lastCapturedById: input.viewer.userId,
      },
    });

    await tx.weeklyKpiSnapshot.update({
      where: { id: row.section.snapshot.id },
      data: {
        updatedAt: now,
      },
    });

    await tx.kpiCaptureCatalog.update({
      where: { id: catalog.id },
      data: {
        ownerUserId: input.payload.ownerUserId,
        captureMode:
          input.payload.captureMode === "auto"
            ? CaptureMode.auto
            : input.payload.captureMode === "hybrid"
              ? CaptureMode.hybrid
              : input.payload.captureMode === "manual"
                ? CaptureMode.manual
                : undefined,
        automationSource:
          input.payload.automationSource !== undefined
            ? input.payload.automationSource?.trim() || null
            : undefined,
        freshnessSlaHours:
          input.payload.freshnessSlaHours !== undefined
            ? Math.max(1, Math.min(24 * 30, Math.round(input.payload.freshnessSlaHours)))
            : undefined,
        captureNote:
          input.payload.captureNote !== undefined
            ? input.payload.captureNote?.trim() || null
            : undefined,
        lastCapturedAt: now,
        lastCapturedById: input.viewer.userId,
      },
    });
  });

  return { ok: true };
}

async function findRowForEvidence(input: {
  rowId?: string | null;
  department?: PrismaDepartmentKey | null;
  weekStart: string;
}): Promise<{
  id: string;
  rowKey: string;
  section: {
    sectionKey: string;
    rawSectionLabel: string;
    mappedDepartment: PrismaDepartmentKey | null;
    snapshot: {
      id: string;
      weekStart: Date;
    };
  };
}> {
  const weekStartDate = new Date(`${input.weekStart}T00:00:00.000Z`);

  if (input.rowId) {
    const row = await prisma.weeklyKpiRow.findUnique({
      where: { id: input.rowId },
      include: {
        section: {
          include: {
            snapshot: true,
          },
        },
      },
    });

    if (!row) {
      throw new Error("KPI row not found.");
    }

    if (row.section.snapshot.weekStart.getTime() !== weekStartDate.getTime()) {
      throw new Error("rowId does not belong to the selected week.");
    }

    return row;
  }

  if (!input.department) {
    throw new Error("rowId or department is required.");
  }

  const rows = await prisma.weeklyKpiRow.findMany({
    where: {
      section: {
        snapshot: {
          weekStart: weekStartDate,
        },
      },
    },
    include: {
      section: {
        include: {
          snapshot: true,
        },
      },
    },
    orderBy: [{ section: { sortOrder: "asc" } }, { sortOrder: "asc" }],
  });

  const firstForDepartment = rows.find((row) => {
    const mapped = canonicalMappedDepartment(row.section.rawSectionLabel, row.section.mappedDepartment);
    return mapped === input.department;
  });

  if (!firstForDepartment) {
    throw new Error("No KPI rows found for department.");
  }

  return firstForDepartment;
}

export async function createCaptureEvidenceLink(input: {
  viewer: CaptureViewerContext;
  weekStartInput?: string | null;
  department?: string | null;
  rowId?: string | null;
  initiativeId?: string | null;
  taskId?: string | null;
  title: string;
  note?: string | null;
  url: string;
}) {
  const { weekStart } = getWeekBounds(input.weekStartInput);
  await ensureCaptureCatalogForWeek(weekStart);

  const scopedDepartment = resolveScopedDepartment(input.viewer, input.department ?? null);
  if (!scopedDepartment) {
    throw new Error("Forbidden");
  }

  const row = await findRowForEvidence({
    rowId: input.rowId,
    department: scopedDepartment,
    weekStart,
  });
  const catalog = await ensureCatalogForRow({ row });

  if (!canEditCatalogEntry(input.viewer, { department: catalog.department, ownerUserId: catalog.ownerUserId })) {
    throw new Error("Forbidden");
  }

  const rawTitle = input.title.trim();
  if (!rawTitle) {
    throw new Error("title is required.");
  }

  let normalizedUrl: string;
  try {
    normalizedUrl = new URL(input.url.trim()).toString();
  } catch {
    throw new Error("Invalid url.");
  }

  const now = new Date();
  const created = await prisma.$transaction(async (tx) => {
    const evidence = await tx.kpiCaptureEvidence.create({
      data: {
        department: catalog.department,
        weekStart: catalog.weekStart,
        sectionKey: catalog.sectionKey,
        rowKey: catalog.rowKey,
        catalogId: catalog.id,
        weeklyKpiRowId: row.id,
        initiativeId: input.initiativeId?.trim() || null,
        taskId: input.taskId?.trim() || null,
        uploadedById: input.viewer.userId,
        kind: "link",
        title: rawTitle,
        note: input.note?.trim() || null,
        url: normalizedUrl,
      },
      include: {
        uploadedBy: {
          select: {
            name: true,
            email: true,
          },
        },
        initiative: {
          select: {
            name: true,
          },
        },
        task: {
          select: {
            title: true,
          },
        },
      },
    });

    await tx.kpiCaptureCatalog.update({
      where: { id: catalog.id },
      data: {
        lastCapturedAt: now,
        lastCapturedById: input.viewer.userId,
      },
    });

    await tx.weeklyKpiRow.update({
      where: { id: row.id },
      data: {
        lastCapturedAt: now,
        lastCapturedById: input.viewer.userId,
      },
    });

    return evidence;
  });

  return mapCaptureEvidence(created);
}

export async function createCaptureEvidenceUpload(input: {
  viewer: CaptureViewerContext;
  weekStartInput?: string | null;
  department?: string | null;
  rowId?: string | null;
  initiativeId?: string | null;
  taskId?: string | null;
  title: string;
  note?: string | null;
  storagePath: string;
  mimeType: string;
  sizeBytes: number;
  kind: "photo" | "document";
}) {
  const { weekStart } = getWeekBounds(input.weekStartInput);
  await ensureCaptureCatalogForWeek(weekStart);

  const scopedDepartment = resolveScopedDepartment(input.viewer, input.department ?? null);
  if (!scopedDepartment) {
    throw new Error("Forbidden");
  }

  const row = await findRowForEvidence({
    rowId: input.rowId,
    department: scopedDepartment,
    weekStart,
  });
  const catalog = await ensureCatalogForRow({ row });

  if (!canEditCatalogEntry(input.viewer, { department: catalog.department, ownerUserId: catalog.ownerUserId })) {
    throw new Error("Forbidden");
  }

  const rawTitle = input.title.trim();
  if (!rawTitle) {
    throw new Error("title is required.");
  }

  const now = new Date();
  const created = await prisma.$transaction(async (tx) => {
    const evidence = await tx.kpiCaptureEvidence.create({
      data: {
        department: catalog.department,
        weekStart: catalog.weekStart,
        sectionKey: catalog.sectionKey,
        rowKey: catalog.rowKey,
        catalogId: catalog.id,
        weeklyKpiRowId: row.id,
        initiativeId: input.initiativeId?.trim() || null,
        taskId: input.taskId?.trim() || null,
        uploadedById: input.viewer.userId,
        kind: input.kind,
        title: rawTitle,
        note: input.note?.trim() || null,
        storagePath: input.storagePath,
        mimeType: input.mimeType,
        sizeBytes: input.sizeBytes,
      },
      include: {
        uploadedBy: {
          select: {
            name: true,
            email: true,
          },
        },
        initiative: {
          select: {
            name: true,
          },
        },
        task: {
          select: {
            title: true,
          },
        },
      },
    });

    await tx.kpiCaptureCatalog.update({
      where: { id: catalog.id },
      data: {
        lastCapturedAt: now,
        lastCapturedById: input.viewer.userId,
      },
    });

    await tx.weeklyKpiRow.update({
      where: { id: row.id },
      data: {
        lastCapturedAt: now,
        lastCapturedById: input.viewer.userId,
      },
    });

    return evidence;
  });

  return mapCaptureEvidence(created);
}

export async function getCaptureEvidenceById(input: { id: string }) {
  return prisma.kpiCaptureEvidence.findUnique({
    where: { id: input.id },
    include: {
      uploadedBy: {
        select: {
          id: true,
          name: true,
          email: true,
          department: true,
        },
      },
      catalog: {
        select: {
          department: true,
          ownerUserId: true,
        },
      },
    },
  });
}

export function canAccessEvidence(viewer: CaptureViewerContext, input: {
  department: PrismaDepartmentKey;
  ownerUserId: string | null;
  uploadedById: string;
}): boolean {
  if (viewer.role === "owner") {
    return true;
  }

  const viewerDepartment = resolveViewerDepartment(viewer);
  if (viewerDepartment !== input.department) {
    return false;
  }

  if (viewer.role === "leader") {
    return true;
  }

  if (viewer.role === "employee") {
    return input.uploadedById === viewer.userId || input.ownerUserId === viewer.userId;
  }

  return false;
}

export async function deleteCaptureEvidence(input: {
  viewer: CaptureViewerContext;
  evidenceId: string;
}) {
  const evidence = await getCaptureEvidenceById({ id: input.evidenceId });
  if (!evidence) {
    throw new Error("Evidence not found.");
  }

  if (
    !canAccessEvidence(input.viewer, {
      department: evidence.department,
      ownerUserId: evidence.catalog?.ownerUserId ?? null,
      uploadedById: evidence.uploadedById,
    })
  ) {
    throw new Error("Forbidden");
  }

  const canManage = canManageDepartment(input.viewer, evidence.department);
  const isUploader = evidence.uploadedById === input.viewer.userId;
  if (!canManage && !isUploader) {
    throw new Error("Forbidden");
  }

  await prisma.kpiCaptureEvidence.delete({
    where: { id: evidence.id },
  });

  return {
    storagePath: evidence.storagePath,
  };
}

export async function startCaptureAutomationRun(input: {
  department: PrismaDepartmentKey;
  weekStart: string;
  source: string;
  triggeredById: string;
}) {
  const weekStartDate = new Date(`${input.weekStart}T00:00:00.000Z`);
  return prisma.kpiCaptureAutomationRun.create({
    data: {
      department: input.department,
      weekStart: weekStartDate,
      source: input.source,
      status: CaptureAutomationRunStatus.running,
      triggeredById: input.triggeredById,
      startedAt: new Date(),
    },
  });
}

export async function completeCaptureAutomationRunSuccess(input: {
  runId: string;
  department: PrismaDepartmentKey;
  weekStart: string;
  source: string;
  sectionKey?: string;
  rowsTouched?: number;
}) {
  const weekStartDate = new Date(`${input.weekStart}T00:00:00.000Z`);
  await ensureCaptureCatalogForWeek(input.weekStart);

  const rowWhere: Prisma.WeeklyKpiRowWhereInput = {
    section: {
      snapshot: {
        weekStart: weekStartDate,
      },
    },
  };

  const rows = await prisma.weeklyKpiRow.findMany({
    where: rowWhere,
    include: {
      section: true,
    },
    orderBy: [{ section: { sortOrder: "asc" } }, { sortOrder: "asc" }],
  });

  const affectedRows = rows.filter((row) => {
    const mappedDepartment = canonicalMappedDepartment(row.section.rawSectionLabel, row.section.mappedDepartment);
    if (mappedDepartment !== input.department) {
      return false;
    }

    if (!input.sectionKey) {
      return true;
    }

    return row.section.sectionKey === input.sectionKey;
  });

  const now = new Date();
  const rowIds = affectedRows.map((row) => row.id);

  if (rowIds.length > 0) {
    await prisma.$transaction([
      prisma.weeklyKpiRow.updateMany({
        where: {
          id: {
            in: rowIds,
          },
        },
        data: {
          lastAutomationAt: now,
          lastCapturedAt: now,
        },
      }),
      prisma.kpiCaptureCatalog.updateMany({
        where: {
          weekStart: weekStartDate,
          department: input.department,
          ...(input.sectionKey ? { sectionKey: input.sectionKey } : {}),
        },
        data: {
          lastAutomationAt: now,
          lastCapturedAt: now,
          automationSource: input.source,
        },
      }),
    ]);
  }

  await prisma.kpiCaptureAutomationRun.update({
    where: { id: input.runId },
    data: {
      status: CaptureAutomationRunStatus.success,
      rowsTouched: input.rowsTouched ?? rowIds.length,
      completedAt: now,
    },
  });
}

export async function completeCaptureAutomationRunFailure(input: {
  runId: string;
  message: string;
}) {
  await prisma.kpiCaptureAutomationRun.update({
    where: { id: input.runId },
    data: {
      status: CaptureAutomationRunStatus.failed,
      errorMessage: input.message,
      completedAt: new Date(),
    },
  });
}

export async function getOwnerCaptureRollup(input: {
  viewer: CaptureViewerContext;
  weekStartInput?: string | null;
}): Promise<OwnerCaptureRollupDTO> {
  if (input.viewer.role !== "owner") {
    throw new Error("Forbidden");
  }

  const { weekStart, weekEnd } = getWeekBounds(input.weekStartInput);
  await ensureCaptureCatalogForWeek(weekStart);
  const departments = await Promise.all(
    ACTIVE_CAPTURE_DEPARTMENTS.map(async (department) => {
      const board = await getCaptureBoard({
        viewer: input.viewer,
        weekStartInput: weekStart,
        requestedDepartment: department,
        ensureCatalog: false,
      });

      const latestRun = board.automationRuns[0] ?? null;
      const hasCriticalError = board.summary.errorRows > 0 || latestRun?.status === "failed";
      const hasAttention = board.summary.staleRows > 0 || board.summary.pendingRows > 0;
      const automationHealth: OwnerCaptureRollupDTO["departments"][number]["automationHealth"] = hasCriticalError
        ? "down"
        : hasAttention
          ? "attention"
          : "healthy";

      return {
        department: department as OwnerCaptureRollupDTO["departments"][number]["department"],
        label: getDepartmentLabel(department as OwnerCaptureRollupDTO["departments"][number]["department"]),
        totalRows: board.summary.totalRows,
        capturedRows: board.summary.capturedRows,
        pendingRows: board.summary.pendingRows,
        inProgressRows: board.summary.inProgressRows,
        staleRows: board.summary.staleRows,
        errorRows: board.summary.errorRows,
        atRiskRows: board.summary.atRiskRows,
        capturePct: board.summary.capturePct,
        automationHealth,
        lastAutomationAt:
          board.rows
            .map((row) => row.lastAutomationAt)
            .filter((value): value is string => Boolean(value))
            .sort()
            .at(-1) ?? latestRun?.completedAt ?? null,
      };
    })
  );

  return {
    weekStart,
    weekEnd,
    departments,
  };
}

export async function listCaptureAssignees(input: {
  viewer: CaptureViewerContext;
  requestedDepartment?: string | null;
}): Promise<CaptureAssigneeDTO[]> {
  const department = resolveScopedDepartment(input.viewer, input.requestedDepartment ?? null);
  if (!department) {
    throw new Error("Forbidden");
  }

  if (!canManageDepartment(input.viewer, department)) {
    throw new Error("Forbidden");
  }

  const users = await prisma.user.findMany({
    where: {
      status: "active",
      ...(department === PrismaDepartmentKey.capital_humano
        ? {
            department: {
              in: [PrismaDepartmentKey.capital_humano, PrismaDepartmentKey.administracion],
            },
          }
        : {
            department,
          }),
    },
    select: {
      id: true,
      name: true,
      email: true,
      department: true,
    },
    orderBy: [{ name: "asc" }, { email: "asc" }],
    take: 250,
  });

  return users.map((user) => ({
    id: user.id,
    name: user.name?.trim() || user.email,
    department: (normalizeDepartment(user.department) ?? department) as CaptureAssigneeDTO["department"],
  }));
}

export async function listCaptureAutomationRuns(input: {
  viewer: CaptureViewerContext;
  requestedDepartment?: string | null;
  weekStartInput?: string | null;
}): Promise<CaptureAutomationRunDTO[]> {
  const department = resolveScopedDepartment(input.viewer, input.requestedDepartment ?? null);
  if (!department) {
    throw new Error("Forbidden");
  }

  const { weekStart } = getWeekBounds(input.weekStartInput);
  const weekStartDate = new Date(`${weekStart}T00:00:00.000Z`);

  const runs = await prisma.kpiCaptureAutomationRun.findMany({
    where: {
      department,
      weekStart: weekStartDate,
    },
    include: {
      triggeredBy: {
        select: {
          name: true,
          email: true,
        },
      },
    },
    orderBy: [{ startedAt: "desc" }],
    take: 80,
  });

  return runs.map(mapAutomationRun);
}
