import { describe, expect, it, vi } from "vitest";
import { slackApprovalNativeRuntime } from "./approval-handler.runtime.js";

type SlackPayload = {
  text: string;
  blocks?: unknown;
};
type ChatUpdatePayload = {
  channel?: string;
  ts?: string;
  text?: string;
  blocks?: unknown;
};
const SLACK_CHAT_UPDATE_TEXT_LIMIT = 4000;

function findSlackActionsBlock(blocks: Array<{ type?: string; elements?: unknown[] }>) {
  return blocks.find((block) => block.type === "actions");
}

function readChatUpdatePayload(
  chatUpdate: { mock: { calls: unknown[][] } },
  index: number,
): ChatUpdatePayload {
  const call = chatUpdate.mock.calls[index];
  if (!call) {
    throw new Error(`Expected Slack chat.update call #${index + 1}`);
  }
  const [payload] = call;
  if (!payload || typeof payload !== "object") {
    throw new Error(`Expected Slack chat.update payload #${index + 1}`);
  }
  return payload as ChatUpdatePayload;
}

describe("slackApprovalNativeRuntime", () => {
  it("renders only the allowed pending actions", async () => {
    const payload = (await slackApprovalNativeRuntime.presentation.buildPendingPayload({
      cfg: {} as never,
      accountId: "default",
      context: {
        app: {} as never,
        config: {} as never,
      },
      request: {
        id: "req-1",
        request: {
          command: "echo hi",
        },
        createdAtMs: 0,
        expiresAtMs: 60_000,
      },
      approvalKind: "exec",
      nowMs: 0,
      view: {
        approvalKind: "exec",
        approvalId: "req-1",
        commandText: "echo hi",
        metadata: [],
        actions: [
          {
            decision: "allow-once",
            label: "Allow Once",
            command: "/approve req-1 allow-once",
            style: "success",
          },
          {
            decision: "deny",
            label: "Deny",
            command: "/approve req-1 deny",
            style: "danger",
          },
        ],
      } as never,
    })) as SlackPayload;

    expect(payload.text).toContain("*Exec approval required*");
    const actionsBlock = findSlackActionsBlock(
      payload.blocks as Array<{ type?: string; elements?: unknown[] }>,
    );
    const labels = (actionsBlock?.elements ?? []).map((element) =>
      typeof element === "object" &&
      element &&
      typeof (element as { text?: { text?: unknown } }).text?.text === "string"
        ? (element as { text: { text: string } }).text.text
        : "",
    );

    expect(labels).toEqual(["Allow Once", "Deny"]);
    expect(JSON.stringify(payload.blocks)).not.toContain("Allow Always");
  });

  it("renders resolved updates without interactive blocks", async () => {
    const result = await slackApprovalNativeRuntime.presentation.buildResolvedResult({
      cfg: {} as never,
      accountId: "default",
      context: {
        app: {} as never,
        config: {} as never,
      },
      request: {
        id: "req-1",
        request: {
          command: "echo hi",
        },
        createdAtMs: 0,
        expiresAtMs: 60_000,
      },
      resolved: {
        id: "req-1",
        decision: "allow-once",
        resolvedBy: "U123APPROVER",
        ts: 0,
      } as never,
      view: {
        approvalKind: "exec",
        approvalId: "req-1",
        decision: "allow-once",
        commandText: "echo hi",
        resolvedBy: "U123APPROVER",
      } as never,
      entry: {
        channelId: "D123APPROVER",
        messageTs: "1712345678.999999",
      },
    });

    expect(result.kind).toBe("update");
    if (result.kind !== "update") {
      throw new Error("expected Slack resolved update payload");
    }
    const payload = result.payload as SlackPayload;
    expect(payload.text).toContain("*Exec approval: Allowed once*");
    expect(payload.text).toContain("Resolved by <@U123APPROVER>.");
    expect(
      (payload.blocks as Array<{ type?: string }>).some((block) => block.type === "actions"),
    ).toBe(false);
  });

  it("caps resolved update fallback text to Slack chat.update limits while preserving blocks", async () => {
    const blocks = [
      {
        type: "section",
        text: {
          type: "mrkdwn",
          text: "*Command*\n```short preview```",
        },
      },
    ];
    const chatUpdate = vi.fn(async (_payload: { text: string; blocks: typeof blocks }) => ({}));
    const context = {
      app: {
        client: {
          chat: {
            update: chatUpdate,
          },
        },
      },
      config: {},
    } as never;

    await slackApprovalNativeRuntime.transport.updateEntry?.({
      cfg: {} as never,
      accountId: "default",
      context,
      entry: {
        channelId: "C123",
        messageTs: "1712345678.999999",
      },
      payload: {
        text: "a".repeat(SLACK_CHAT_UPDATE_TEXT_LIMIT),
        blocks,
      },
      phase: "resolved",
    });

    await slackApprovalNativeRuntime.transport.updateEntry?.({
      cfg: {} as never,
      accountId: "default",
      context,
      entry: {
        channelId: "C123",
        messageTs: "1712345678.999999",
      },
      payload: {
        text: "a".repeat(5000),
        blocks,
      },
      phase: "resolved",
    });

    const firstUpdate = readChatUpdatePayload(chatUpdate, 0);
    const secondUpdate = readChatUpdatePayload(chatUpdate, 1);
    expect(firstUpdate.channel).toBe("C123");
    expect(firstUpdate.ts).toBe("1712345678.999999");
    expect(firstUpdate.text).toBe("a".repeat(SLACK_CHAT_UPDATE_TEXT_LIMIT));
    expect(firstUpdate.blocks).toBe(blocks);
    expect(secondUpdate.channel).toBe("C123");
    expect(secondUpdate.ts).toBe("1712345678.999999");
    expect(secondUpdate.text).toMatch(/…$/);
    expect(secondUpdate.blocks).toBe(blocks);
    expect(secondUpdate.text).toHaveLength(SLACK_CHAT_UPDATE_TEXT_LIMIT);
  });

  it("keeps pending metadata context within Slack Block Kit limits", async () => {
    const payload = (await slackApprovalNativeRuntime.presentation.buildPendingPayload({
      cfg: {} as never,
      accountId: "default",
      context: {
        app: {} as never,
        config: {} as never,
      },
      request: {
        id: "req-1",
        request: {
          command: "echo hi",
        },
        createdAtMs: 0,
        expiresAtMs: 60_000,
      },
      approvalKind: "exec",
      nowMs: 0,
      view: {
        approvalKind: "exec",
        approvalId: "req-1",
        commandText: "echo hi",
        metadata: Array.from({ length: 12 }, (_entry, index) => ({
          label: `Metadata ${index + 1}`,
          value: index === 0 ? "x".repeat(3100) : `value-${index + 1}`,
        })),
        actions: [
          {
            decision: "allow-once",
            label: "Allow Once",
            command: "/approve req-1 allow-once",
            style: "success",
          },
        ],
      } as never,
    })) as SlackPayload;

    const contextBlock = (payload.blocks as Array<{ type?: string; elements?: unknown[] }>).find(
      (block) => block.type === "context",
    );
    const elements = contextBlock?.elements as Array<{ text?: string }> | undefined;

    expect(elements).toHaveLength(10);
    expect(elements?.[0]?.text).toHaveLength(3000);
    expect(elements?.[0]?.text?.endsWith("…")).toBe(true);
    expect(elements?.at(-1)?.text).toBe("…+3 more");
  });
});
