/**
 * Org / project usage rollups from AgentRun cost facts (ADR-0021).
 * Operational usage accounting, not payment collection. Organizations may use
 * either their own provider credentials or a distinct platform-managed
 * connection; commercial billing remains outside the pilot scope.
 */
import type { Prisma, PrismaClient } from "@prisma/client";

export interface ProjectUsageRow {
  readonly projectId: string;
  readonly projectName: string;
  readonly folderId: string | null;
  readonly runCount: number;
  readonly runsWithCost: number;
  readonly runsWithoutCost: number;
  readonly inputTokens: number;
  readonly outputTokens: number;
  readonly costUsd: number | null;
}

export interface UsageReport {
  readonly from: string | null;
  readonly to: string | null;
  readonly projects: readonly ProjectUsageRow[];
  readonly totals: {
    readonly runCount: number;
    readonly runsWithCost: number;
    readonly runsWithoutCost: number;
    readonly inputTokens: number;
    readonly outputTokens: number;
    readonly costUsd: number | null;
  };
}

export async function getOrgUsage(
  prisma: PrismaClient,
  input: {
    readonly organizationId: string;
    readonly from?: Date | undefined;
    readonly to?: Date | undefined;
    readonly folderId?: string | undefined;
  },
): Promise<UsageReport> {
  const projectWhere: Prisma.ProjectWhereInput = {
    organizationId: input.organizationId,
    ...(input.folderId !== undefined ? { folderId: input.folderId } : {}),
  };
  const projects = await prisma.project.findMany({
    where: projectWhere,
    select: { id: true, name: true, folderId: true },
    orderBy: { name: "asc" },
  });
  if (projects.length === 0) {
    return emptyReport(input.from, input.to);
  }

  const runWhere: Prisma.AgentRunWhereInput = {
    projectId: { in: projects.map((p) => p.id) },
    ...(input.from !== undefined || input.to !== undefined
      ? {
          startedAt: {
            ...(input.from !== undefined ? { gte: input.from } : {}),
            ...(input.to !== undefined ? { lte: input.to } : {}),
          },
        }
      : {}),
  };

  const runs = await prisma.agentRun.findMany({
    where: runWhere,
    select: {
      projectId: true,
      inputTokens: true,
      outputTokens: true,
      costUsd: true,
    },
  });

  type MutableRow = {
    projectId: string;
    projectName: string;
    folderId: string | null;
    runCount: number;
    runsWithCost: number;
    runsWithoutCost: number;
    inputTokens: number;
    outputTokens: number;
    costUsd: number | null;
  };

  const byProject = new Map<string, MutableRow>();
  for (const p of projects) {
    byProject.set(p.id, {
      projectId: p.id,
      projectName: p.name,
      folderId: p.folderId,
      runCount: 0,
      runsWithCost: 0,
      runsWithoutCost: 0,
      inputTokens: 0,
      outputTokens: 0,
      costUsd: null,
    });
  }

  for (const run of runs) {
    const row = byProject.get(run.projectId);
    if (row === undefined) continue;
    row.runCount += 1;
    row.inputTokens += run.inputTokens ?? 0;
    row.outputTokens += run.outputTokens ?? 0;
    if (run.costUsd === null) {
      row.runsWithoutCost += 1;
    } else {
      row.runsWithCost += 1;
      const cost = Number(run.costUsd);
      row.costUsd = (row.costUsd ?? 0) + cost;
    }
  }

  const projectsOut: ProjectUsageRow[] = [...byProject.values()];
  let runCount = 0;
  let runsWithCost = 0;
  let runsWithoutCost = 0;
  let inputTokens = 0;
  let outputTokens = 0;
  let costUsd: number | null = null;
  for (const row of projectsOut) {
    runCount += row.runCount;
    runsWithCost += row.runsWithCost;
    runsWithoutCost += row.runsWithoutCost;
    inputTokens += row.inputTokens;
    outputTokens += row.outputTokens;
    if (row.costUsd !== null) {
      costUsd = (costUsd ?? 0) + row.costUsd;
    }
  }

  return {
    from: input.from?.toISOString() ?? null,
    to: input.to?.toISOString() ?? null,
    projects: projectsOut,
    totals: {
      runCount,
      runsWithCost,
      runsWithoutCost,
      inputTokens,
      outputTokens,
      costUsd,
    },
  };
}

export async function getProjectUsage(
  prisma: PrismaClient,
  input: {
    readonly organizationId: string;
    readonly projectId: string;
    readonly from?: Date | undefined;
    readonly to?: Date | undefined;
  },
): Promise<ProjectUsageRow> {
  const project = await prisma.project.findFirst({
    where: { id: input.projectId, organizationId: input.organizationId },
    select: { id: true, name: true, folderId: true },
  });
  if (project === null) {
    throw new Error(`project not found: ${input.projectId}`);
  }
  const report = await getOrgUsage(prisma, {
    organizationId: input.organizationId,
    from: input.from,
    to: input.to,
  });
  const row = report.projects.find((p) => p.projectId === project.id);
  return (
    row ?? {
      projectId: project.id,
      projectName: project.name,
      folderId: project.folderId,
      runCount: 0,
      runsWithCost: 0,
      runsWithoutCost: 0,
      inputTokens: 0,
      outputTokens: 0,
      costUsd: null,
    }
  );
}

function emptyReport(from?: Date, to?: Date): UsageReport {
  return {
    from: from?.toISOString() ?? null,
    to: to?.toISOString() ?? null,
    projects: [],
    totals: {
      runCount: 0,
      runsWithCost: 0,
      runsWithoutCost: 0,
      inputTokens: 0,
      outputTokens: 0,
      costUsd: null,
    },
  };
}
