import { test, describe, expect, beforeAll, afterAll } from "bun:test";
import * as q from "./query";
import { parse as $parse, type ParseOptions } from "bknd/utils";
import type { PrimaryFieldType } from "modules";
import type { Generated } from "kysely";
import { disableConsoleLog, enableConsoleLog } from "core/utils/test";

const parse = (v: unknown, o: ParseOptions = {}) =>
   $parse(q.repoQuery, v, {
      ...o,
      withDefaults: false,
   });

// compatibility
const decode = (input: any, output: any) => {
   expect(parse(input)).toEqual(output);
};

beforeAll(() => disableConsoleLog());
afterAll(() => enableConsoleLog());

describe("server/query", () => {
   test("limit & offset", () => {
      //expect(() => parse({ limit: false })).toThrow();
      expect(parse({ limit: "11" })).toEqual({ limit: 11 });
      expect(parse({ limit: 20 })).toEqual({ limit: 20 });
      expect(parse({ offset: "1" })).toEqual({ offset: 1 });
   });

   test("select", () => {
      expect(parse({ select: "id" })).toEqual({ select: ["id"] });
      expect(parse({ select: "id,title" })).toEqual({ select: ["id", "title"] });
      expect(parse({ select: "id,title,desc" })).toEqual({ select: ["id", "title", "desc"] });
      expect(parse({ select: ["id", "title"] })).toEqual({ select: ["id", "title"] });
   });

   test("join", () => {
      expect(parse({ join: "id" })).toEqual({ join: ["id"] });
      expect(parse({ join: "id,title" })).toEqual({ join: ["id", "title"] });
      expect(parse({ join: ["id", "title"] })).toEqual({ join: ["id", "title"] });
   });

   test("sort", () => {
      expect(parse({ sort: "id" }).sort).toEqual({
         by: "id",
         dir: "asc",
      });
      expect(parse({ sort: "-id" }).sort).toEqual({
         by: "id",
         dir: "desc",
      });
      expect(parse({ sort: { by: "title" } }).sort).toEqual({
         by: "title",
         dir: "asc",
      });
      expect(
         parse(
            { sort: { by: "id" } },
            {
               withDefaults: true,
            },
         ).sort,
      ).toEqual({
         by: "id",
         dir: "asc",
      });
      expect(parse({ sort: { by: "count", dir: "desc" } }).sort).toEqual({
         by: "count",
         dir: "desc",
      });
      // invalid gives default
      expect(parse({ sort: "not allowed" }).sort).toEqual({
         by: "id",
         dir: "asc",
      });

      // json
      expect(parse({ sort: JSON.stringify({ by: "count", dir: "desc" }) }).sort).toEqual({
         by: "count",
         dir: "desc",
      });
   });

   test("sort2", () => {
      const _dflt = { sort: { by: "id", dir: "asc" } } as const;

      decode({ sort: "" }, _dflt);
      decode({ sort: "name" }, { sort: { by: "name", dir: "asc" } });
      decode({ sort: "-name" }, { sort: { by: "name", dir: "desc" } });
      decode({ sort: "-posts.name" }, { sort: { by: "posts.name", dir: "desc" } });
      decode({ sort: "-1name" }, _dflt);
      decode({ sort: { by: "name", dir: "desc" } }, { sort: { by: "name", dir: "desc" } });
   });

   test("where", () => {
      expect(parse({ where: { id: 1 } }).where).toEqual({
         id: { $eq: 1 },
      });
      expect(parse({ where: JSON.stringify({ id: 1 }) }).where).toEqual({
         id: { $eq: 1 },
      });

      expect(parse({ where: { count: { $gt: 1 } } }).where).toEqual({
         count: { $gt: 1 },
      });
      expect(parse({ where: JSON.stringify({ count: { $gt: 1 } }) }).where).toEqual({
         count: { $gt: 1 },
      });
   });

   test("template", () => {
      expect(
         q.repoQuery.template(
            {},
            {
               withOptional: true,
            },
         ),
      ).toEqual({
         limit: 10,
         offset: 0,
         sort: { by: "id", dir: "asc" },
         where: {},
         select: [],
         join: [],
      });
   });

   test("with", () => {
      let example = {
         limit: 10,
         with: {
            posts: { limit: "10", with: ["comments"] },
         },
      };
      expect(parse(example)).toEqual({
         limit: 10,
         with: {
            posts: {
               limit: 10,
               with: {
                  comments: {},
               },
            },
         },
      });

      decode({ with: ["posts"] }, { with: { posts: {} } });
      decode({ with: { posts: {} } }, { with: { posts: {} } });
      decode({ with: { posts: { limit: 1 } } }, { with: { posts: { limit: 1 } } });
      decode(
         {
            with: {
               posts: {
                  with: {
                     images: {
                        limit: "10",
                        select: "id",
                     },
                  },
               },
            },
         },
         {
            with: {
               posts: {
                  with: {
                     images: {
                        limit: 10,
                        select: ["id"],
                     },
                  },
               },
            },
         },
      );

      // over http
      {
         const output = { with: { images: {} } };
         decode({ with: "images" }, output);
         decode({ with: '["images"]' }, output);
         decode({ with: ["images"] }, output);
         decode({ with: { images: {} } }, output);
      }

      {
         const output = { with: { images: {}, comments: {} } };
         decode({ with: "images,comments" }, output);
         decode({ with: ["images", "comments"] }, output);
         decode({ with: '["images", "comments"]' }, output);
         decode({ with: { images: {}, comments: {} } }, output);
      }
   });

   test("types", () => {
      const id = 1 as PrimaryFieldType;
      const id2 = "1" as unknown as Generated<string>;

      const c: q.RepoQueryIn = {
         where: {
            // @ts-expect-error only primitives are allowed for $eq
            something: [],
            // this gets ignored
            another: undefined,
            // @ts-expect-error null is not a valid value
            null_is_okay: null,
            some_id: id,
            another_id: id2,
         },
      };

      const d: q.RepoQuery = {
         where: {
            // @ts-expect-error only primitives are allowed for $eq
            something: [],
            // this gets ignored
            another: undefined,
            // @ts-expect-error null is not a valid value
            null_is_okay: null,
            some_id: id,
            another_id: id2,
         },
      };
   });
});
