import { readFile } from "node:fs/promises";
import { join } from "node:path";

import type { ReportData } from "../build/types.js";
import { CorruptReviewDraftError } from "../review/draft.js";
import {
  buildPromotionPreview,
  buildPromotionPreviewsForDraft,
  PromotionError,
  stagePromotionRequest,
  type PromotionPreview,
} from "../review/promotion.js";
import { loadReviewDraft, type ReviewDraft } from "../review/index.js";

export type PromotionRequestBody = {
  findingId?: unknown;
};

export class InvalidPromotionRequestError extends Error {
  readonly code: string;

  constructor(code: string, message: string) {
    super(message);
    this.name = "InvalidPromotionRequestError";
    this.code = code;
  }
}

async function loadReportData(reportDir: string): Promise<ReportData> {
  const reportPath = join(reportDir, "data/report.json");
  let raw: string;
  try {
    raw = await readFile(reportPath, "utf8");
  } catch {
    throw new InvalidPromotionRequestError(
      "missing-report-data",
      "report data is unavailable for promotion mutations",
    );
  }

  let parsed: unknown;
  try {
    parsed = JSON.parse(raw) as unknown;
  } catch {
    throw new InvalidPromotionRequestError("invalid-report-data", "report data is not valid JSON");
  }

  if (
    typeof parsed !== "object" ||
    parsed === null ||
    !("findings" in parsed) ||
    !("coverageOutcomes" in parsed) ||
    !("harnessOutcomes" in parsed)
  ) {
    throw new InvalidPromotionRequestError(
      "invalid-report-data",
      "report data is missing required outcome collections",
    );
  }

  return parsed as ReportData;
}

function parseFindingId(body: PromotionRequestBody): string {
  if (typeof body.findingId !== "string" || body.findingId.trim().length === 0) {
    throw new InvalidPromotionRequestError(
      "invalid-finding-id",
      "promotion requests require a non-empty findingId",
    );
  }
  return body.findingId;
}

export type PromotionMutationResult = {
  status: "staged";
  requestPath: string;
  draft: ReviewDraft;
  preview: PromotionPreview;
};

export type PromotionDraftPayload = {
  draft: ReviewDraft;
  promotionPreviews: Record<string, PromotionPreview>;
};

export async function readPromotionDraftPayload(reportDir: string): Promise<PromotionDraftPayload> {
  const [draft, reportData] = await Promise.all([
    loadReviewDraft(reportDir),
    loadReportData(reportDir),
  ]);
  const promotionPreviews = buildPromotionPreviewsForDraft(reportData, draft);
  return { draft, promotionPreviews };
}

export async function readPromotionPreview(
  reportDir: string,
  findingId: string,
): Promise<PromotionPreview> {
  const reportData = await loadReportData(reportDir);
  return buildPromotionPreview(reportDir, reportData, findingId);
}

export async function handlePromotionMutation(
  reportDir: string,
  body: PromotionRequestBody,
): Promise<PromotionMutationResult> {
  const findingId = parseFindingId(body);
  const reportData = await loadReportData(reportDir);
  const preview = await buildPromotionPreview(reportDir, reportData, findingId);
  const draft = await stagePromotionRequest(reportDir, reportData, findingId);

  return {
    status: "staged",
    requestPath: join(".invariantum/promotion-requests", `${findingId}.json`),
    draft,
    preview,
  };
}

export { CorruptReviewDraftError, PromotionError };
