import $ from "jquery";
import Model from "@memserver/model";
import Memserver from "@memserver/server";
import Response from "@memserver/response";
import { module, test } from "qunitx";
import setupForTests from "./helpers/setup-for-tests";

module("@memserver/server | params, headers, queryParams tests", function (hooks) {
  setupForTests(hooks);

  const AUTHENTICATION_TOKEN = "ec25fc7b-6ee2-4bda-b57c-6c9867b30ff4";
  const AJAX_AUTHORIZATION_HEADERS = {
    "Content-Type": "application/json",
    Authorization: `Token ${AUTHENTICATION_TOKEN}`,
  };

  const ETHEREUM_ACCOUNT_FIXTURES = [
    {
      id: 1,
      address: "0x7be8315acfef37816c9ad4dc5e82195f2a52934c5d0c74883f9978675e26d600",
    },
  ];
  const USER_FIXTURES = [
    {
      id: 1,
      email: "contact@izelnakri.com",
      username: "izelnakri",
      authentication_token: AUTHENTICATION_TOKEN,
    },
  ];
  const PHOTO_FIXTURES = [
    {
      id: 1,
      name: "Ski trip",
      href: "ski-trip.jpeg",
      is_public: false,
      user_id: 1,
    },
    {
      id: 2,
      name: "Family photo",
      href: "family-photo.jpeg",
      is_public: true,
      user_id: 1,
    },
    {
      id: 3,
      name: "Selfie",
      href: "selfie.jpeg",
      is_public: false,
      user_id: 1,
    },
  ];
  const PHOTO_COMMENT_FIXTURES = [
    {
      uuid: "499ec646-493f-4eea-b92e-e383d94182f4",
      content: "What a nice photo!",
      photo_id: 1,
      user_id: 1,
    },
    {
      uuid: "77653ad3-47e4-4ec2-b49f-57ea36a627e7",
      content: "I agree",
      photo_id: 1,
      user_id: 2,
    },
    {
      uuid: "d351963d-e725-4092-a37c-1ca1823b57d3",
      content: "I was kidding",
      photo_id: 1,
      user_id: 1,
    },
    {
      uuid: "374c7f4a-85d6-429a-bf2a-0719525f5f29",
      content: "Interesting indeed",
      photo_id: 2,
      user_id: 1,
    },
  ];

  function prepare() {
    class User extends Model {
      static findFromHeaderToken(headers): any | false {
        const authorizationHeader = headers.Authorization;
        const token = authorizationHeader ? authorizationHeader.slice(6) : false;

        return this.findBy({ authentication_token: token }) || false;
      }
    }
    class Photo extends Model {
      static defaultAttributes = {
        is_public: true,
        name() {
          return "Some default name";
        },
      };
    }
    class PhotoComment extends Model {
      static defaultAttributes = {
        is_important: true,
      };
    }

    return { User, Photo, PhotoComment };
  }

  module("Simple Rest Server", function (hooks) {
    function prepareRESTServerTest() {
      const { User, Photo } = prepare();

      PHOTO_FIXTURES.forEach((photo) => Photo.insert(photo));
      USER_FIXTURES.forEach((user) => User.insert(user));

      const Server = new Memserver({
        routes() {
          this.post("/photos", ({ headers, params, queryParams }) => {
            const user = User.findFromHeaderToken(headers);

            if (!user || !queryParams.is_admin) {
              return Response(401, { error: "Unauthorized" });
            }

            console.log("user is", user);
            const photo = Photo.insert(Object.assign({}, params.photo, { user_id: user.id }));

            return { photo: Photo.serializer(photo) };
          });

          this.get("/photos", ({ headers, queryParams }) => {
            const user = User.findFromHeaderToken(headers);

            if (!user) {
              return Response(404, { error: "Not found" });
            }

            const photos = Photo.findAll(Object.assign({}, { user_id: user.id }, queryParams));

            if (!photos || photos.length === 0) {
              return Response(404, { error: "Not found" });
            }

            return { photos: Photo.serializer(photos) };
          });

          this.get("/photos/:id", ({ headers, params, queryParams }) => {
            const user = User.findFromHeaderToken(headers);

            if (!user) {
              return Response(401, { error: "Unauthorized" });
            } else if (queryParams.nonce === 123123123) {
              const photo = Photo.findBy({ id: params.id, user_id: user.id });

              return photo
                ? { photo: Photo.serializer(photo) }
                : Response(404, { error: "Not found" });
            }

            return Response(404, { error: "Not found" });
          });

          this.put("/photos/:id", ({ headers, params, queryParams }) => {
            const user = User.findFromHeaderToken(headers);
            const validRequest =
              user &&
              queryParams.nonce === 123123123 &&
              Photo.findBy({ id: params.id, user_id: user.id });

            if (validRequest) {
              return { photo: Photo.serializer(Photo.update(params.photo)) };
            }

            return Response(500, { error: "Unexpected error occured" });
          });

          this.delete("/photos/:id", ({ headers, params, queryParams }) => {
            const user = User.findFromHeaderToken(headers);

            if (!(queryParams.nonce === 123123123)) {
              return Response(500, { error: "Invalid nonce to delete a photo" });
            } else if (!user || !Photo.findBy({ id: params.id, user_id: user.id })) {
              return Response(404, { error: "Not found" });
            }

            Photo.delete({ id: params.id }); // NOTE: what to do with this response
          });
        },
      });

      return { Server, User, Photo };
    }

    test("[MemServer.Server] POST /resources work with custom headers, queryParams and responses", async function (assert) {
      assert.expect(8);

      const { Photo, User, Server } = prepareRESTServerTest();

      this.Server = Server;

      assert.equal(Photo.count(), 3);

      await $.ajax({
        type: "POST",
        url: "/photos",
        headers: { "Content-Type": "application/json" },
      }).catch((jqXHR) => {
        assert.equal(jqXHR.status, 401);
        assert.deepEqual(jqXHR.responseJSON, { error: "Unauthorized" });
      });

      await $.ajax({
        type: "POST",
        url: "/photos",
        headers: AJAX_AUTHORIZATION_HEADERS,
      }).catch((jqXHR) => {
        assert.equal(jqXHR.status, 401);
        assert.deepEqual(jqXHR.responseJSON, { error: "Unauthorized" });
      });

      await $.ajax({
        type: "POST",
        url: "/photos?is_admin=true",
        headers: AJAX_AUTHORIZATION_HEADERS,
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 201);
        assert.deepEqual(data, { photo: Photo.serializer(Photo.find(4)) });
        assert.equal(Photo.count(), 4);
      });
    });

    test("[MemServer.Server] GET /resources works with custom headers, queryParams and responses", async function (assert) {
      assert.expect(6);

      const { Photo, User, Server } = prepareRESTServerTest();

      this.Server = Server;

      await $.ajax({
        type: "GET",
        url: "/photos",
        headers: { "Content-Type": "application/json" },
      }).catch((jqXHR) => {
        assert.equal(jqXHR.status, 404);
        assert.deepEqual(jqXHR.responseJSON, { error: "Not found" });
      });

      await $.ajax({
        type: "GET",
        url: "/photos?is_public=false",
        headers: AJAX_AUTHORIZATION_HEADERS,
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 200);
        assert.deepEqual(data, { photos: Photo.serializer(Photo.findAll({ is_public: false })) });
      });

      await $.ajax({
        type: "GET",
        url: "/photos?href=family-photo.jpeg",
        headers: AJAX_AUTHORIZATION_HEADERS,
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 200);
        assert.deepEqual(data, {
          photos: Photo.serializer(Photo.findAll({ href: "family-photo.jpeg" })),
        });
      });
    });

    test("[MemServer.Server] GET /resources/:id works with custom headers, queryParams and responses", async function (assert) {
      assert.expect(4);

      const { Photo, User, Server } = prepareRESTServerTest();

      this.Server = Server;

      await $.ajax({
        type: "GET",
        url: "/photos/1",
        headers: AJAX_AUTHORIZATION_HEADERS,
      }).catch((jqXHR) => {
        assert.equal(jqXHR.status, 404);
        assert.deepEqual(jqXHR.responseJSON, { error: "Not found" });
      });

      await $.ajax({
        type: "GET",
        url: "/photos/1?nonce=123123123",
        headers: AJAX_AUTHORIZATION_HEADERS,
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 200);
        assert.deepEqual(data, { photo: Photo.serializer(Photo.find(1)) });
      });
    });

    test("[MemServer.Server] PUT /resources/:id works with custom headers, queryParams and responses", async function (assert) {
      assert.expect(4);

      const { Photo, User, Server } = prepareRESTServerTest();

      this.Server = Server;

      await $.ajax({
        type: "PUT",
        url: "/photos/1",
        headers: AJAX_AUTHORIZATION_HEADERS,
        data: JSON.stringify({ photo: { id: 1, name: "Life" } }),
      }).catch((jqXHR) => {
        assert.equal(jqXHR.status, 500);
        assert.deepEqual(jqXHR.responseJSON, { error: "Unexpected error occured" });
      });

      await $.ajax({
        type: "PUT",
        url: "/photos/1?nonce=123123123",
        headers: AJAX_AUTHORIZATION_HEADERS,
        data: JSON.stringify({ photo: { id: 1, name: "Life" } }),
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 200);
        assert.deepEqual(data, { photo: Photo.serializer(Photo.find(1)) });
      });
    });

    test("[MemServer.Server] DELETE /resources/:id works with custom headers, queryParams and responses", async function (assert) {
      assert.expect(3);

      const { Photo, User, Server } = prepareRESTServerTest();

      this.Server = Server;

      await $.ajax({
        type: "DELETE",
        url: "/photos/1",
        headers: AJAX_AUTHORIZATION_HEADERS,
      }).catch((jqXHR) => {
        assert.equal(jqXHR.status, 500);
        assert.deepEqual(jqXHR.responseJSON, { error: "Invalid nonce to delete a photo" });
      });

      await $.ajax({
        type: "DELETE",
        url: "/photos/1?nonce=123123123",
        headers: AJAX_AUTHORIZATION_HEADERS,
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 204);
      });
    });
  });

  module("Edge-case Server tests", function () {
    function prepareEdgeCaseServerTests() {
      const { User, Photo, PhotoComment } = prepare();

      class EthereumAccount extends Model {}

      ETHEREUM_ACCOUNT_FIXTURES.forEach((ethereumAccount) =>
        EthereumAccount.insert(ethereumAccount)
      );
      PHOTO_FIXTURES.forEach((photo) => Photo.insert(photo));
      USER_FIXTURES.forEach((user) => User.insert(user));
      PHOTO_COMMENT_FIXTURES.forEach((photoComment) => PhotoComment.insert(photoComment));

      const Server = new Memserver({
        routes() {
          this.get("/ethereum-accounts", ({ queryParams }) => {
            const ethereumAccounts = EthereumAccount.findAll({ address: queryParams.address });

            return { ethereum_accounts: EthereumAccount.serializer(ethereumAccounts) };
          });

          this.get("/ethereum-accounts/:address", ({ params }) => {
            const ethereumAccount = EthereumAccount.findBy({ address: params.address });

            return { ethereum_account: EthereumAccount.serializer(ethereumAccount) };
          });

          this.get("/photos", ({ queryParams }) => {
            const photos = Photo.find(queryParams.ids || []);

            if (!photos || photos.length === 0) {
              return Response(404, { error: "Not found" });
            }

            return { photos: Photo.serializer(photos) };
          });

          this.get("/photo-comments/:uuid", ({ params }) => {
            const photoComment = PhotoComment.findBy({ uuid: params.uuid });

            return { photo_comment: PhotoComment.serializer(photoComment) };
          });

          this.get("/photo-comments", ({ queryParams }) => {
            const photoComments = PhotoComment.findAll({ uuid: queryParams.uuid });

            return { photo_comments: PhotoComment.serializer(photoComments) };
          });
        },
      });

      return { EthereumAccount, Server, User, Photo, PhotoComment };
    }

    test("[MemServer.Server Edge cases] Server works for coalasceFindRequests routes", async function (assert) {
      assert.expect(6);

      const { Photo, PhotoComment, Server } = prepareEdgeCaseServerTests();

      this.Server = Server;

      await $.ajax({
        type: "GET",
        url: "/photos",
        headers: { "Content-Type": "application/json" },
      }).catch((jqXHR) => {
        assert.equal(jqXHR.status, 404);
        assert.deepEqual(jqXHR.responseJSON, { error: "Not found" });
      });

      await $.ajax({
        type: "GET",
        url: "/photos?ids[]=1&ids[]=2",
        headers: { "Content-Type": "application/json" },
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 200);
        assert.deepEqual(jqXHR.responseJSON, { photos: Photo.serializer(Photo.find([1, 2])) });
      });

      await $.ajax({
        type: "GET",
        url: "/photos?ids[]=2&ids[]=3",
        headers: { "Content-Type": "application/json" },
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 200);
        assert.deepEqual(jqXHR.responseJSON, { photos: Photo.serializer(Photo.find([2, 3])) });
      });
    });

    test("[MemServer.Server Edge cases] Server converts empty strings to null during a request and formats query params", async function (assert) {
      assert.expect(4);

      const { Photo, Server } = prepareEdgeCaseServerTests();

      this.Server = Server;
      this.Server.post("/photos", ({ params, queryParams }) => {
        assert.deepEqual(params, { name: null, title: "Cool" });
        assert.deepEqual(queryParams, { is_important: true, filter: 32 });

        return { photo: Photo.serializer(Photo.insert(params)) };
      });

      await $.ajax({
        type: "POST",
        url: "/photos?is_important=true&filter=32",
        data: { name: "", title: "Cool" },
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 201);
        assert.equal(Photo.count(), 4);
      });
    });

    test("[MemServer.Server Edge cases] Server casts uuids correctly as params", async function (assert) {
      assert.expect(2);

      const { Photo, PhotoComment, Server } = prepareEdgeCaseServerTests();

      this.Server = Server;

      const targetComment = PhotoComment.findBy({ uuid: "499ec646-493f-4eea-b92e-e383d94182f4" });

      await $.ajax({
        type: "GET",
        url: "/photo-comments/499ec646-493f-4eea-b92e-e383d94182f4",
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 200);
        assert.deepEqual(data, { photo_comment: PhotoComment.serializer(targetComment) });
      });
    });

    test("[MemServer.Server Edge cases] Server casts uuids correct as queryParams", async function (assert) {
      assert.expect(2);

      const { Photo, PhotoComment, Server } = prepareEdgeCaseServerTests();

      this.Server = Server;

      const targetComments = PhotoComment.findAll({ uuid: "499ec646-493f-4eea-b92e-e383d94182f4" });

      await $.ajax({
        type: "GET",
        url: "/photo-comments?uuid=499ec646-493f-4eea-b92e-e383d94182f4",
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 200);
        assert.deepEqual(data, { photo_comments: PhotoComment.serializer(targetComments) });
      });
    });

    test("[MemServer.Server Edga cases] Server casts ethereum adresses correctly as string request.params", async function (assert) {
      assert.expect(2);

      const { EthereumAccount, Photo, PhotoComment, Server } = prepareEdgeCaseServerTests();

      this.Server = Server;

      const targetAccount = EthereumAccount.findBy({
        address: "0x7be8315acfef37816c9ad4dc5e82195f2a52934c5d0c74883f9978675e26d600",
      });

      await $.ajax({
        type: "GET",
        url:
          "/ethereum-accounts/0x7be8315acfef37816c9ad4dc5e82195f2a52934c5d0c74883f9978675e26d600",
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 200);
        assert.deepEqual(data, { ethereum_account: EthereumAccount.serializer(targetAccount) });
      });
    });

    test("[MemServer.Server Edge cases] Server casts ethereum addresses correctly as string in request.queryParams", async function (assert) {
      assert.expect(2);

      const { EthereumAccount, Photo, PhotoComment, Server } = prepareEdgeCaseServerTests();

      this.Server = Server;

      const targetAccounts = EthereumAccount.findAll({
        address: "0x7be8315acfef37816c9ad4dc5e82195f2a52934c5d0c74883f9978675e26d600",
      });

      await $.ajax({
        type: "GET",
        url:
          "/ethereum-accounts?address=0x7be8315acfef37816c9ad4dc5e82195f2a52934c5d0c74883f9978675e26d600",
      }).then((data, textStatus, jqXHR) => {
        assert.equal(jqXHR.status, 200);
        assert.deepEqual(data, { ethereum_accounts: EthereumAccount.serializer(targetAccounts) });
      });
    });
  });
});
