import { describe, test, expect, beforeEach } from "bun:test";
import { Hono } from "hono";
import { apiResponse } from "../../src/modules/api-utils/apiResponse";
import type { ContentfulStatusCode } from "hono/utils/http-status";

/**
 * Tests for the CW callback route handler.
 *
 * We import the route handler and mount it on a Hono app to test via
 * the app.request() convenience method.
 */

// We need to test the internal helper functions. Since they are not
// exported, we test them through the route handler's observable behavior.
import callbackRoute from "../../src/api/cw/callback";

describe("CW callback route handler", () => {
  let app: Hono;

  beforeEach(() => {
    app = new Hono();
    // Replicate the error handling from server.ts
    app.onError((err, c) => {
      if ((err as any).status) {
        const body = apiResponse.error(err);
        return c.json(body, body.status as ContentfulStatusCode);
      }
      return c.json(apiResponse.internalError(), 500);
    });
    app.route("/", callbackRoute);
    // Clear the env var before each test
    delete (process.env as Record<string, any>).CW_CALLBACK_SECRET;
  });

  // -------------------------------------------------------------------
  // Secret validation
  // -------------------------------------------------------------------
  test("rejects when secret does not match CW_CALLBACK_SECRET", async () => {
    process.env.CW_CALLBACK_SECRET = "correct-secret";
    const res = await app.request("/callback/wrong-secret/opportunity", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({}),
    });
    expect(res.status).toBe(401);
    const body = await res.json();
    expect(body.message).toContain("Invalid callback secret");
  });

  test("accepts when secret matches CW_CALLBACK_SECRET", async () => {
    process.env.CW_CALLBACK_SECRET = "correct-secret";
    const res = await app.request("/callback/correct-secret/opportunity", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ Action: "updated", ID: 123 }),
    });
    expect(res.status).toBe(200);
  });

  test("accepts any secret when CW_CALLBACK_SECRET is not configured", async () => {
    const res = await app.request("/callback/any-secret/opportunity", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ Action: "created" }),
    });
    expect(res.status).toBe(200);
  });

  // -------------------------------------------------------------------
  // Resource validation
  // -------------------------------------------------------------------
  test("accepts 'opportunity' resource", async () => {
    const res = await app.request("/callback/test/opportunity", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({}),
    });
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.data.resource).toBe("opportunity");
  });

  test("accepts 'ticket' resource", async () => {
    const res = await app.request("/callback/test/ticket", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({}),
    });
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.data.resource).toBe("ticket");
  });

  test("accepts 'company' resource", async () => {
    const res = await app.request("/callback/test/company", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({}),
    });
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.data.resource).toBe("company");
  });

  test("accepts 'activity' resource", async () => {
    const res = await app.request("/callback/test/activity", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({}),
    });
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.data.resource).toBe("activity");
  });

  test("rejects invalid resource type", async () => {
    const res = await app.request("/callback/test/invalidtype", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({}),
    });
    // Zod validation should fail
    expect(res.status).toBeGreaterThanOrEqual(400);
  });

  // -------------------------------------------------------------------
  // Body parsing
  // -------------------------------------------------------------------
  test("parses JSON body fields", async () => {
    const res = await app.request("/callback/test/opportunity", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        Action: "updated",
        Type: "opportunity",
        ID: 42,
        MemberId: "jroberts",
        MessageId: "msg-123",
      }),
    });
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.data.summary.action).toBe("updated");
    expect(body.data.summary.type).toBe("opportunity");
    expect(body.data.summary.id).toBe(42);
    expect(body.data.summary.memberId).toBe("jroberts");
    expect(body.data.summary.messageId).toBe("msg-123");
  });

  test("parses Entity field from JSON string", async () => {
    const entity = {
      CompanyName: "Acme Corp",
      StatusName: "Active",
      UpdatedBy: "admin",
    };
    const res = await app.request("/callback/test/company", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        Action: "updated",
        Entity: JSON.stringify(entity),
      }),
    });
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.data.summary.entitySummary).toBe("Acme Corp");
    expect(body.data.summary.entityStatus).toBe("Active");
    expect(body.data.summary.entityUpdatedBy).toBe("admin");
  });

  test("handles Entity as inline object", async () => {
    const res = await app.request("/callback/test/company", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        Action: "created",
        Entity: { CompanyName: "Direct Corp", Status: "New" },
      }),
    });
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.data.summary.entitySummary).toBe("Direct Corp");
    expect(body.data.summary.entityStatus).toBe("New");
  });

  test("returns secretValidated field based on env presence", async () => {
    delete (process.env as Record<string, any>).CW_CALLBACK_SECRET;
    const res = await app.request("/callback/test/opportunity", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({}),
    });
    const body = await res.json();
    expect(body.data.secretValidated).toBe(false);

    process.env.CW_CALLBACK_SECRET = "secret";
    const res2 = await app.request("/callback/secret/opportunity", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({}),
    });
    const body2 = await res2.json();
    expect(body2.data.secretValidated).toBe(true);
  });

  test("returns receivedAt timestamp", async () => {
    const res = await app.request("/callback/test/opportunity", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({}),
    });
    const body = await res.json();
    expect(body.data.receivedAt).toBeDefined();
    // Should be a valid ISO date string
    expect(new Date(body.data.receivedAt).toISOString()).toBe(
      body.data.receivedAt,
    );
  });

  test("handles non-JSON body gracefully", async () => {
    const res = await app.request("/callback/test/opportunity", {
      method: "POST",
      headers: { "Content-Type": "text/plain" },
      body: "this is not json",
    });
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.data.summary).toBeNull();
  });

  test("handles empty body gracefully", async () => {
    const res = await app.request("/callback/test/opportunity", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: "",
    });
    expect(res.status).toBe(200);
    const body = await res.json();
    expect(body.data.summary).toBeNull();
  });
});
