import { describe, expect, it, vi, afterEach } from "vitest";
import type { PrismaClient } from "@prisma/client";
import type { PermissionAuthorizer } from "../../src/permission.js";
import type { RuntimeSettings } from "../../src/settings/runtime.js";
import { sendApprovalCard, resolveCard, type CardActionEvent, type FeishuRuntime } from "../../src/feishu/client.js";
import { ApprovalManager, makeTriggerHandler } from "../../src/feishu/trigger.js";

describe("Feishu approval cards", () => {
  afterEach(() => {
    vi.useRealTimers();
  });

  it("sendApprovalCard builds correct card JSON", async () => {
    const messageCreate = vi.fn(async () => ({ data: { message_id: "message-1" } }));
    const rt = mockRuntime({ messageCreate });

    await expect(sendApprovalCard(rt, "chat-1", "Approve?", "Please approve.", [
      { label: "Approve", value: "approve", style: "primary" },
      { label: "Reject", value: "reject", style: "danger" },
      { label: "Later", value: "later" },
    ])).resolves.toBe("message-1");

    expect(messageCreate).toHaveBeenCalledTimes(1);
    const payload = messageCreate.mock.calls[0]?.[0] as { data: { content: string } };
    expect(payload).toMatchObject({
      params: { receive_id_type: "chat_id" },
      data: { receive_id: "chat-1", msg_type: "interactive" },
    });
    expect(JSON.parse(payload.data.content)).toEqual({
      config: { wide_screen_mode: true },
      header: { title: { content: "Approve?", tag: "plain_text" }, template: "orange" },
      elements: [
        { tag: "markdown", content: "Please approve." },
        {
          tag: "action",
          actions: [
            {
              tag: "button",
              text: { tag: "plain_text", content: "Approve" },
              type: "primary",
              value: { approval_action: "approve" },
            },
            {
              tag: "button",
              text: { tag: "plain_text", content: "Reject" },
              type: "danger",
              value: { approval_action: "reject" },
            },
            {
              tag: "button",
              text: { tag: "plain_text", content: "Later" },
              type: "default",
              value: { approval_action: "later" },
            },
          ],
        },
      ],
    });
  });

  it("resolveCard patches with resolved state", async () => {
    const messagePatch = vi.fn(async () => ({}));
    const rt = mockRuntime({ messagePatch });

    await resolveCard(rt, "message-1", "OK", "Approved", "green", "ou_user");

    expect(messagePatch).toHaveBeenCalledTimes(1);
    expect(messagePatch).toHaveBeenCalledWith({
      path: { message_id: "message-1" },
      params: { msg_type: "interactive" },
      data: {
        content: JSON.stringify({
          config: { wide_screen_mode: true },
          header: { title: { content: "OK Approved", tag: "plain_text" }, template: "green" },
          elements: [{ tag: "markdown", content: "OK **Approved** by ou_user" }],
        }),
      },
    });
  });

  it("ApprovalManager register+resolve lifecycle", async () => {
    const manager = new ApprovalManager({ timeoutMs: 10_000 });
    const result = manager.register("message-1", "chat-1");

    expect(manager.hasPending("message-1")).toBe(true);
    manager.resolve("message-1", "approve", "ou_user");

    await expect(result).resolves.toEqual({ action: "approve", resolvedBy: "ou_user" });
    expect(manager.hasPending("message-1")).toBe(false);
  });

  it("ApprovalManager timeout", async () => {
    vi.useFakeTimers();
    const manager = new ApprovalManager({ timeoutMs: 100 });
    const result = manager.register("message-1", "chat-1");
    const assertion = expect(result).rejects.toThrow("approval timed out after 100ms");

    await vi.advanceTimersByTimeAsync(100);

    await assertion;
    expect(manager.hasPending("message-1")).toBe(false);
  });

  it("routes card actions through the trigger handler", async () => {
    const messagePatch = vi.fn(async () => ({}));
    const rt = mockRuntime({ messagePatch });
    const trigger = makeTriggerHandler({
      prisma: {} as PrismaClient,
      settings: {} as RuntimeSettings,
      logger: silentLogger(),
      authorizer: allowAllAuthorizer(),
      projectWorkspaceRoot: "/tmp",
      publicBaseUrl: "https://educraft.example.test",
      siloOrganizationId: "org_test_default",
      runAgent: async () => ({
        status: "completed",
        text: "",
        usage: { inputTokens: 0, outputTokens: 0 },
        numTurns: 0,
      }),
    });
    const result = trigger.approvalManager.register("message-1", "chat-1");

    await trigger.onCardAction(makeCardActionEvent("message-1", "approve", "ou_user"), rt);

    await expect(result).resolves.toEqual({ action: "approve", resolvedBy: "ou_user" });
    expect(messagePatch).toHaveBeenCalledTimes(1);
    const payload = messagePatch.mock.calls[0]?.[0] as { data: { content: string } };
    expect(payload).toMatchObject({
      path: { message_id: "message-1" },
      params: { msg_type: "interactive" },
    });
    expect(JSON.parse(payload.data.content)).toEqual({
      config: { wide_screen_mode: true },
      header: { title: { content: "✅ Approved", tag: "plain_text" }, template: "green" },
      elements: [{ tag: "markdown", content: "✅ **Approved** by ou_user" }],
    });
    expect(trigger.approvalManager.hasPending("message-1")).toBe(false);
  });
});

function makeCardActionEvent(messageId: string, action: string, openId: string): CardActionEvent {
  return {
    operator: { open_id: openId },
    action: { value: { approval_action: action }, tag: "button" },
    context: { open_message_id: messageId, open_chat_id: "chat-1" },
  };
}

function mockRuntime(options: {
  readonly messageCreate?: (payload: unknown) => Promise<unknown>;
  readonly messagePatch?: (payload: unknown) => Promise<unknown>;
} = {}): FeishuRuntime {
  return {
    client: {
      im: {
        v1: {
          message: {
            create: options.messageCreate ?? vi.fn(async () => ({ data: { message_id: "message-1" } })),
            patch: options.messagePatch ?? vi.fn(async () => ({})),
          },
        },
      },
    } as unknown as FeishuRuntime["client"],
    logger: silentLogger(),
  };
}

function allowAllAuthorizer(): PermissionAuthorizer {
  return {
    async can(req) {
      return {
        allowed: true,
        reason: "test allow",
        action: req.action,
        resource: req.resource,
        actor: req.actor,
        organizationId: "org_test_default",
        actorUserId: "user-1",
        principals: [{ type: "USER", id: "ou_user" }],
        requiredRole: "EDIT",
        effectiveRole: "EDIT",
      };
    },
  };
}

function silentLogger(): FeishuRuntime["logger"] {
  return {
    info() {},
    warn() {},
    error() {},
    debug() {},
    fatal() {},
    child() { return this; },
    level: "silent",
  } as unknown as FeishuRuntime["logger"];
}
