import { Collection, Document, Pin, User } from "@server/models";
import {
  buildAdmin,
  buildCollection,
  buildDocument,
  buildDraftDocument,
  buildPin,
  buildUser,
} from "@server/test/factories";
import { getTestServer } from "@server/test/support";

const server = getTestServer();

describe("#pins.create", () => {
  let admin: User;
  let user: User;
  let anotherUser: User;
  let document: Document;
  let collection: Collection;

  beforeEach(async () => {
    admin = await buildAdmin();
    [user, anotherUser] = await Promise.all([
      buildUser({ teamId: admin.teamId }),
      buildUser(),
    ]);
    collection = await buildCollection({
      createdById: admin.id,
      teamId: admin.teamId,
    });
    document = await buildDocument({
      createdById: admin.id,
      teamId: admin.teamId,
      collectionId: collection.id,
    });
  });

  it("should fail with status 401 unauthorized when user token is missing", async () => {
    const res = await server.post("/api/pins.create", {
      body: {
        documentId: "foo",
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(401);
    expect(body.message).toEqual("Authentication required");
  });

  it("should fail with status 400 bad request when documentId is not suppled", async () => {
    const res = await server.post("/api/pins.create", {
      body: {
        token: user.getJwtToken(),
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(400);
    expect(body.message).toEqual("documentId: required");
  });

  it("should fail with status 400 bad request when documentId is invalid", async () => {
    const res = await server.post("/api/pins.create", {
      body: {
        token: user.getJwtToken(),
        documentId: "foo",
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(400);
    expect(body.message).toEqual("documentId: must be uuid or url slug");
  });

  it("should fail with status 400 bad request when index is invalid", async () => {
    const res = await server.post("/api/pins.create", {
      body: {
        token: user.getJwtToken(),
        documentId: "foo1234567",
        index: "😀",
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(400);
    expect(body.message).toEqual("index: must be between x20 to x7E ASCII");
  });

  it("should fail with status 403 forbidden when user is disallowed to read the document", async () => {
    const res = await server.post("/api/pins.create", {
      body: {
        token: anotherUser.getJwtToken(),
        documentId: document.id,
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(403);
    expect(body.message).toEqual("Authorization error");
  });

  it("should fail with status 403 forbidden when user is disallowed to update the collection", async () => {
    const res = await server.post("/api/pins.create", {
      body: {
        token: user.getJwtToken(),
        documentId: document.id,
        collectionId: collection.id,
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(403);
    expect(body.message).toEqual("Authorization error");
  });

  it("should fail with status 403 forbidden when user is disallowed to pin the document", async () => {
    const draft = await buildDraftDocument({
      createdById: admin.id,
      teamId: admin.teamId,
      collectionId: collection.id,
    });
    const res = await server.post("/api/pins.create", {
      body: {
        token: admin.getJwtToken(),
        // A draft document cannot be pinned, neither by a member nor by an admin
        documentId: draft.id,
        collectionId: collection.id,
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(403);
    expect(body.message).toEqual("Authorization error");
  });

  it("should fail with status 403 forbidden when user is disallowed to pin the document to home page", async () => {
    const res = await server.post("/api/pins.create", {
      body: {
        token: user.getJwtToken(),
        documentId: document.id,
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(403);
    expect(body.message).toEqual("Authorization error");
  });

  it("should succeed with status 200 ok when user is allowed to pin", async () => {
    const res = await server.post("/api/pins.create", {
      body: {
        token: admin.getJwtToken(),
        documentId: document.id,
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(200);
    expect(body.data).toBeTruthy();
    expect(body.data.documentId).toEqual(document.id);
    expect(body.data.collectionId).toBeNull();
  });

  it("should succeed with status 200 ok when valid collectionId is supplied", async () => {
    const res = await server.post("/api/pins.create", {
      body: {
        token: admin.getJwtToken(),
        documentId: document.id,
        collectionId: collection.id,
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(200);
    expect(body.data).toBeTruthy();
    expect(body.data.documentId).toEqual(document.id);
    expect(body.data.collectionId).toEqual(collection.id);
  });
});

describe("#pins.list", () => {
  let user: User;
  let pins: Pin[];
  let docs: Document[];
  let collection: Collection;

  beforeEach(async () => {
    user = await buildUser();
    collection = await buildCollection({
      teamId: user.teamId,
      createdById: user.id,
    });
    docs = await Promise.all([
      buildDocument({
        teamId: user.teamId,
        collectionId: collection.id,
      }),
      buildDocument({
        teamId: user.teamId,
        collectionId: collection.id,
      }),
    ]);
    pins = await Promise.all([
      buildPin({
        createdById: user.id,
        documentId: docs[0].id,
        teamId: user.teamId,
      }),
      buildPin({
        createdById: user.id,
        documentId: docs[1].id,
        teamId: user.teamId,
      }),
    ]);
  });

  it("should fail with status 401 unauthorized when user token is missing", async () => {
    const res = await server.post("/api/pins.list", {
      body: {},
    });
    const body = await res.json();
    expect(res.status).toEqual(401);
    expect(body.message).toEqual("Authentication required");
  });

  it("should succeed with status 200 ok returning pinned documents", async () => {
    const res = await server.post("/api/pins.list", {
      body: {
        token: user.getJwtToken(),
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(200);
    expect(body.data).toBeTruthy();
    expect(body.data.pins).toBeTruthy();
    expect(body.data.pins).toHaveLength(2);
    const pinIds = body.data.pins.map((p: any) => p.id);
    expect(pinIds).toContain(pins[0].id);
    expect(pinIds).toContain(pins[1].id);
    const docIds = body.data.documents.map((d: any) => d.id);
    expect(docIds).toContain(docs[0].id);
    expect(docIds).toContain(docs[1].id);
  });

  it("should succeed with status 200 ok returning pinned documents filtered by collectionId supplied", async () => {
    const res = await server.post("/api/pins.list", {
      body: {
        token: user.getJwtToken(),
        collectionId: collection.id,
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(200);
    expect(body.data).toBeTruthy();
    expect(body.data.pins).toBeTruthy();
    expect(body.data.pins).toHaveLength(0);
  });
});

describe("#pins.update", () => {
  let user: User;
  let admin: User;
  let pin: Pin;

  beforeEach(async () => {
    user = await buildUser();
    admin = await buildAdmin();
    const collection = await buildCollection({
      createdById: admin.id,
      teamId: admin.teamId,
    });
    const doc = await buildDocument({
      createdById: admin.id,
      teamId: admin.teamId,
      collectionId: collection.id,
    });
    pin = await buildPin({
      teamId: admin.teamId,
      createdById: admin.id,
      documentId: doc.id,
      index: "a",
    });
  });

  it("should fail with status 401 unauthorized when user token is missing", async () => {
    const res = await server.post("/api/pins.update", {
      body: {
        id: pin.id,
        index: "i",
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(401);
    expect(body.message).toEqual("Authentication required");
  });

  it("should fail with status 400 bad request when id is missing", async () => {
    const res = await server.post("/api/pins.update", {
      body: {
        token: admin.getJwtToken(),
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(400);
    expect(body.message).toEqual("id: Required");
  });

  it("should fail with status 400 bad request when index is missing", async () => {
    const res = await server.post("/api/pins.update", {
      body: {
        token: admin.getJwtToken(),
        id: pin.id,
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(400);
    expect(body.message).toEqual("index: Required");
  });

  it("should fail with status 400 bad request when an invalid index is sent", async () => {
    const res = await server.post("/api/pins.update", {
      body: {
        token: admin.getJwtToken(),
        id: pin.id,
        index: "😀",
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(400);
    expect(body.message).toEqual("index: must be between x20 to x7E ASCII");
  });

  it("should fail with status 403 forbidden when user is disallowed to update the pin", async () => {
    const res = await server.post("/api/pins.update", {
      body: {
        token: user.getJwtToken(),
        id: pin.id,
        index: "b",
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(403);
    expect(body.message).toEqual("Authorization error");
  });

  it("should succeed with status 200 ok and when user is allowed to update the pin", async () => {
    const res = await server.post("/api/pins.update", {
      body: {
        token: admin.getJwtToken(),
        id: pin.id,
        index: "b",
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(200);
    expect(body.data).toBeTruthy();
    expect(body.data.id).toEqual(pin.id);
    expect(body.data.index).toEqual("b");
  });
});

describe("#pins.delete", () => {
  let admin: User;
  let pin: Pin;

  beforeEach(async () => {
    admin = await buildAdmin();
    pin = await buildPin({
      teamId: admin.teamId,
      createdById: admin.id,
    });
  });

  it("should fail with status 401 unauthorized when user token is missing", async () => {
    const res = await server.post("/api/pins.delete", {
      body: {},
    });
    const body = await res.json();
    expect(res.status).toEqual(401);
    expect(body.message).toEqual("Authentication required");
  });

  it("should fail with status 400 bad request when id is missing", async () => {
    const res = await server.post("/api/pins.delete", {
      body: {
        token: admin.getJwtToken(),
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(400);
    expect(body.message).toEqual("id: Required");
  });

  it("should fail with status 403 forbidden when user is disallowed to delete the pin", async () => {
    const user = await buildUser({
      teamId: admin.teamId,
    });
    const res = await server.post("/api/pins.delete", {
      body: {
        token: user.getJwtToken(),
        id: pin.id,
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(403);
    expect(body.message).toEqual("Authorization error");
  });

  it("should succeed with status 200 ok when user is allowed to delete the pin", async () => {
    const res = await server.post("/api/pins.delete", {
      body: {
        token: admin.getJwtToken(),
        id: pin.id,
      },
    });
    const body = await res.json();
    expect(res.status).toEqual(200);
    expect(body.success).toEqual(true);
  });
});
