import { describe, it, expect, vi, beforeEach } from "vitest";

vi.mock("~/lib/cloudinary.server", () => ({
  verifyCloudinaryWebhookSignature: vi.fn(),
  deleteCloudinaryImageByUrl: vi.fn().mockResolvedValue(undefined),
}));
vi.mock("~/models/user", () => ({
  findUserById: vi.fn(),
  updateUser: vi.fn(),
}));
vi.mock("~/models/team", () => ({
  findTeamById: vi.fn(),
  updateTeam: vi.fn(),
}));
vi.mock("~/lib/logger", () => ({
  logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn() },
}));

import { action } from "../api.webhooks.cloudinary";
import {
  verifyCloudinaryWebhookSignature,
  deleteCloudinaryImageByUrl,
} from "~/lib/cloudinary.server";
import { findUserById, updateUser } from "~/models/user";
import { findTeamById, updateTeam } from "~/models/team";

function makeRequest(body: object) {
  return new Request("http://test/api/webhooks/cloudinary", {
    method: "POST",
    body: JSON.stringify(body),
    headers: {
      "Content-Type": "application/json",
      "X-Cld-Signature": "sig",
      "X-Cld-Timestamp": "1234567890",
    },
  });
}

function approvedUserPayload(overrides: object = {}) {
  return {
    secure_url: "https://res.cloudinary.com/demo/image/upload/v1/avatars/user-1.jpg",
    moderation_status: "approved",
    context: { avatar_target: "user", entity_id: "user-abc" },
    ...overrides,
  };
}

function approvedTeamPayload(overrides: object = {}) {
  return {
    secure_url: "https://res.cloudinary.com/demo/image/upload/v1/avatars/team-1.jpg",
    moderation_status: "approved",
    context: { avatar_target: "team", entity_id: "team-xyz" },
    ...overrides,
  };
}

beforeEach(() => {
  vi.clearAllMocks();
  vi.mocked(verifyCloudinaryWebhookSignature).mockReturnValue(true);
  vi.mocked(updateUser).mockResolvedValue({} as never);
  vi.mocked(updateTeam).mockResolvedValue({} as never);
  vi.mocked(findUserById).mockResolvedValue(null as never);
  vi.mocked(findTeamById).mockResolvedValue(null as never);
});

describe("Cloudinary webhook — signature", () => {
  it("returns 401 when signature is invalid", async () => {
    vi.mocked(verifyCloudinaryWebhookSignature).mockReturnValue(false);
    const res = await action({ request: makeRequest(approvedUserPayload()), params: {}, context: {} } as never);
    expect(res.status).toBe(401);
  });
});

describe("Cloudinary webhook — moderation", () => {
  it("returns ignored when moderation_status is rejected", async () => {
    const res = await action({
      request: makeRequest({ ...approvedUserPayload(), moderation_status: "rejected" }),
      params: {},
      context: {},
    } as never);
    const body = await res.json();
    expect(body).toEqual({ status: "ignored" });
  });

  it("returns ignored when moderation_status is pending", async () => {
    const res = await action({
      request: makeRequest({ ...approvedUserPayload(), moderation_status: "pending" }),
      params: {},
      context: {},
    } as never);
    const body = await res.json();
    expect(body).toEqual({ status: "ignored" });
  });

  it("reads approval from nested moderation array", async () => {
    vi.mocked(findUserById).mockResolvedValue({ customAvatarUrl: null } as never);
    const payload = {
      secure_url: "https://res.cloudinary.com/demo/image/upload/v1/avatars/user-1.jpg",
      moderation: [{ status: "approved" }],
      context: { avatar_target: "user", entity_id: "user-abc" },
    };
    const res = await action({ request: makeRequest(payload), params: {}, context: {} } as never);
    const body = await res.json();
    expect(body).toEqual({ status: "updated" });
  });
});

describe("Cloudinary webhook — missing fields", () => {
  it("returns 400 when secure_url is absent", async () => {
    const res = await action({
      request: makeRequest({ moderation_status: "approved", context: { avatar_target: "user", entity_id: "u1" } }),
      params: {},
      context: {},
    } as never);
    expect(res.status).toBe(400);
  });

  it("returns 400 when context is absent", async () => {
    const res = await action({
      request: makeRequest({ moderation_status: "approved", secure_url: "https://res.cloudinary.com/demo/image/upload/v1/avatars/x.jpg" }),
      params: {},
      context: {},
    } as never);
    expect(res.status).toBe(400);
  });

  it("returns 400 for unknown avatar_target", async () => {
    const res = await action({
      request: makeRequest({ ...approvedUserPayload(), context: { avatar_target: "league", entity_id: "l1" } }),
      params: {},
      context: {},
    } as never);
    expect(res.status).toBe(400);
  });
});

describe("Cloudinary webhook — user update", () => {
  it("sets customAvatarUrl and avatarType on the user", async () => {
    vi.mocked(findUserById).mockResolvedValue({ customAvatarUrl: null } as never);
    await action({ request: makeRequest(approvedUserPayload()), params: {}, context: {} } as never);
    expect(updateUser).toHaveBeenCalledWith("user-abc", {
      customAvatarUrl: "https://res.cloudinary.com/demo/image/upload/v1/avatars/user-1.jpg",
      avatarType: "uploaded",
    });
  });

  it("deletes the previous avatar when one exists", async () => {
    const oldUrl = "https://res.cloudinary.com/demo/image/upload/v1/avatars/user-old.jpg";
    vi.mocked(findUserById).mockResolvedValue({ customAvatarUrl: oldUrl } as never);
    await action({ request: makeRequest(approvedUserPayload()), params: {}, context: {} } as never);
    expect(deleteCloudinaryImageByUrl).toHaveBeenCalledWith(oldUrl);
  });

  it("does not call delete when there is no previous avatar", async () => {
    vi.mocked(findUserById).mockResolvedValue({ customAvatarUrl: null } as never);
    await action({ request: makeRequest(approvedUserPayload()), params: {}, context: {} } as never);
    expect(deleteCloudinaryImageByUrl).not.toHaveBeenCalled();
  });

  it("does not delete when previous URL matches new URL", async () => {
    const url = "https://res.cloudinary.com/demo/image/upload/v1/avatars/user-1.jpg";
    vi.mocked(findUserById).mockResolvedValue({ customAvatarUrl: url } as never);
    await action({ request: makeRequest(approvedUserPayload({ secure_url: url })), params: {}, context: {} } as never);
    expect(deleteCloudinaryImageByUrl).not.toHaveBeenCalled();
  });
});

describe("Cloudinary webhook — team update", () => {
  it("sets logoUrl and avatarType on the team", async () => {
    vi.mocked(findTeamById).mockResolvedValue({ logoUrl: null } as never);
    await action({ request: makeRequest(approvedTeamPayload()), params: {}, context: {} } as never);
    expect(updateTeam).toHaveBeenCalledWith("team-xyz", {
      logoUrl: "https://res.cloudinary.com/demo/image/upload/v1/avatars/team-1.jpg",
      avatarType: "uploaded",
    });
  });

  it("deletes the previous team logo when one exists", async () => {
    const oldUrl = "https://res.cloudinary.com/demo/image/upload/v1/avatars/team-old.jpg";
    vi.mocked(findTeamById).mockResolvedValue({ logoUrl: oldUrl } as never);
    await action({ request: makeRequest(approvedTeamPayload()), params: {}, context: {} } as never);
    expect(deleteCloudinaryImageByUrl).toHaveBeenCalledWith(oldUrl);
  });

  it("also handles context in pipe-separated string format", async () => {
    vi.mocked(findUserById).mockResolvedValue({ customAvatarUrl: null } as never);
    const payload = {
      secure_url: "https://res.cloudinary.com/demo/image/upload/v1/avatars/user-1.jpg",
      moderation_status: "approved",
      context: "avatar_target=user|entity_id=user-abc",
    };
    await action({ request: makeRequest(payload), params: {}, context: {} } as never);
    expect(updateUser).toHaveBeenCalledWith("user-abc", expect.objectContaining({ avatarType: "uploaded" }));
  });
});
