import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/core";
import type { Transaction } from "../generated/kysely-tailordb";
import { InvalidEventError, SignatureInvalidError } from "../lib/errors.generated";

// Slack signs event deliveries with `v0=HMAC-SHA256(signingSecret,
// "v0:{timestamp}:{rawBody}")` and a request timestamp header. Requests older
// (or newer) than 5 minutes are rejected to prevent replay.
const SIGNATURE_VERSION = "v0";
const TIMESTAMP_TOLERANCE_SECONDS = 60 * 5;

export interface HandleSlackAppUninstalledInput {
  /** Raw (unparsed) request body of the Slack event delivery. */
  rawBody: string;
  /** `X-Slack-Request-Timestamp` header value (unix seconds). */
  timestamp: string;
  /** `X-Slack-Signature` header value (`v0=<hex>`). */
  signature: string;
  /** Slack app signing secret used to verify the request. */
  signingSecret: string;
}

function hexToBytes(hex: string): Uint8Array<ArrayBuffer> | null {
  if (hex.length === 0 || hex.length % 2 !== 0 || /[^0-9a-fA-F]/.test(hex)) return null;
  const bytes = new Uint8Array(hex.length / 2);
  for (let i = 0; i < bytes.length; i++) {
    bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
  }
  return bytes;
}

/**
 * Verifies the Slack request signature with Web Crypto. `crypto.subtle.verify`
 * performs a constant-time comparison.
 */
async function verifySlackSignature(input: HandleSlackAppUninstalledInput): Promise<boolean> {
  if (!input.signature.startsWith(`${SIGNATURE_VERSION}=`)) return false;
  const signatureBytes = hexToBytes(input.signature.slice(SIGNATURE_VERSION.length + 1));
  if (!signatureBytes) return false;

  const key = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(input.signingSecret),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["verify"],
  );
  const baseString = `${SIGNATURE_VERSION}:${input.timestamp}:${input.rawBody}`;
  return crypto.subtle.verify("HMAC", key, signatureBytes, new TextEncoder().encode(baseString));
}

export async function run(
  db: Transaction,
  input: HandleSlackAppUninstalledInput,
  ctx: CommandContext,
) {
  void ctx;
  if (!input.rawBody || !input.timestamp || !input.signature || !input.signingSecret) {
    return err(new SignatureInvalidError("missing-signature-material"));
  }

  // Replay protection: reject requests whose timestamp is more than 5 minutes
  // away from now (mirrors the inline `new Date()` clock used below).
  const timestampSeconds = Number(input.timestamp);
  if (!Number.isFinite(timestampSeconds)) {
    return err(new SignatureInvalidError("invalid-timestamp"));
  }
  if (Math.abs(Date.now() / 1000 - timestampSeconds) > TIMESTAMP_TOLERANCE_SECONDS) {
    return err(new SignatureInvalidError("stale-timestamp"));
  }

  if (!(await verifySlackSignature(input))) {
    return err(new SignatureInvalidError("signature-mismatch"));
  }

  // The teamId is taken from the signature-verified body only — never from a
  // separately supplied (spoofable) input field.
  let teamId: string | undefined;
  try {
    // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- runtime JSON boundary
    const event = JSON.parse(input.rawBody) as { team_id?: unknown };
    if (typeof event.team_id === "string" && event.team_id) teamId = event.team_id;
  } catch {
    return err(new InvalidEventError("malformed-event-body"));
  }
  if (!teamId) return err(new InvalidEventError("missing-team-id"));

  const workspace = await db
    .selectFrom("SlackWorkspaceIntegration")
    .selectAll()
    .where("teamId", "=", teamId)
    .executeTakeFirst();
  if (!workspace) return ok({ revoked: false, slackWorkspaceIntegration: null });

  const now = new Date();
  const slackWorkspaceIntegration = await db
    .updateTable("SlackWorkspaceIntegration")
    .set({ status: "REVOKED", revokedAt: now, updatedAt: now })
    .where("id", "=", workspace.id)
    .returningAll()
    .executeTakeFirst();
  return ok({ revoked: true, slackWorkspaceIntegration });
}
