export interface GiteaWebhookIngressRetry {
  enabled: boolean;
  maxBodyBytes: number;
  maxRetries: number;
  tryIntervalMs: number;
  dialTimeoutMs: number;
  writeTimeoutMs: number;
  retryStatusCodes: number[];
}

export interface GiteaWebhookCaddyRoute {
  publicPath: string;
  remotePort: number;
}

export function validateGiteaWebhookTiming(
  responseBudgetMs: number,
  ingressRetry: Pick<GiteaWebhookIngressRetry, "maxRetries" | "tryIntervalMs" | "dialTimeoutMs" | "writeTimeoutMs">,
): void {
  if (responseBudgetMs >= 9000) {
    throw new Error("responseBudgetMs must stay below 9000 to leave margin under GitHub's 10 second webhook deadline");
  }
  if (ingressRetry.maxRetries !== 1) {
    throw new Error("ingressRetry.maxRetries must be 1 so the fsync acceptance path has one bounded replay");
  }
  const responseHeaderTimeoutMs = deriveGiteaWebhookResponseHeaderTimeoutMs(responseBudgetMs, ingressRetry);
  const hardBoundMs = (ingressRetry.dialTimeoutMs + ingressRetry.writeTimeoutMs + responseHeaderTimeoutMs) * (ingressRetry.maxRetries + 1)
    + ingressRetry.tryIntervalMs * ingressRetry.maxRetries;
  if (hardBoundMs > responseBudgetMs) {
    throw new Error("Caddy retry attempts and intervals must fit inside responseBudgetMs");
  }
}

export function deriveGiteaWebhookAttemptBudgetMs(
  responseBudgetMs: number,
  ingressRetry: Pick<GiteaWebhookIngressRetry, "maxRetries" | "tryIntervalMs">,
): number {
  const attempts = ingressRetry.maxRetries + 1;
  const intervalBudgetMs = ingressRetry.maxRetries * ingressRetry.tryIntervalMs;
  const attemptBudgetMs = Math.floor((responseBudgetMs - intervalBudgetMs) / attempts);
  if (!Number.isFinite(attemptBudgetMs) || attemptBudgetMs < 1) {
    throw new Error("responseBudgetMs must leave a positive response-header timeout for every Caddy attempt");
  }
  return attemptBudgetMs;
}

export function deriveGiteaWebhookResponseHeaderTimeoutMs(
  responseBudgetMs: number,
  ingressRetry: Pick<GiteaWebhookIngressRetry, "maxRetries" | "tryIntervalMs" | "dialTimeoutMs" | "writeTimeoutMs">,
): number {
  const attemptBudgetMs = deriveGiteaWebhookAttemptBudgetMs(responseBudgetMs, ingressRetry);
  const responseHeaderTimeoutMs = attemptBudgetMs - ingressRetry.dialTimeoutMs - ingressRetry.writeTimeoutMs;
  if (!Number.isFinite(responseHeaderTimeoutMs) || responseHeaderTimeoutMs < 1) {
    throw new Error("dialTimeoutMs and writeTimeoutMs must leave a positive response-header timeout in every Caddy attempt");
  }
  return responseHeaderTimeoutMs;
}

export function renderGiteaWebhookCaddyHandle(
  route: GiteaWebhookCaddyRoute,
  retry: GiteaWebhookIngressRetry,
  responseBudgetMs: number,
  legacyResponseHeaderTimeoutSeconds: number,
): string {
  if (!retry.enabled) {
    return `  handle ${route.publicPath}* {
    reverse_proxy 127.0.0.1:${route.remotePort} {
      transport http {
        response_header_timeout ${legacyResponseHeaderTimeoutSeconds}s
      }
    }
  }`;
  }
  validateGiteaWebhookTiming(responseBudgetMs, retry);
  const responseHeaderTimeoutMs = deriveGiteaWebhookResponseHeaderTimeoutMs(responseBudgetMs, retry);
  const replayBufferBytes = retry.maxBodyBytes + 1;
  const retryStatuses = retry.retryStatusCodes.join(", ");
  return `  handle ${route.publicPath}* {
    request_body {
      max_size ${retry.maxBodyBytes}
    }
    reverse_proxy 127.0.0.1:${route.remotePort} {
      request_buffers ${replayBufferBytes}
      lb_retries ${retry.maxRetries}
      lb_try_duration ${responseBudgetMs}ms
      lb_try_interval ${retry.tryIntervalMs}ms
      lb_retry_match {
        expression \`method('POST') && ({rp.is_transport_error} || {rp.status_code} in [${retryStatuses}])\`
      }
      transport http {
        dial_timeout ${retry.dialTimeoutMs}ms
        write_timeout ${retry.writeTimeoutMs}ms
        response_header_timeout ${responseHeaderTimeoutMs}ms
      }
    }
  }`;
}
