import "reflect-metadata"
import { DataSource } from "../../../src/data-source/DataSource"
import {
    closeTestingConnections,
    createTestingConnections,
    reloadTestingDatabases,
} from "../../utils/test-utils"
import { Post } from "./entity/Post"
import { expect } from "chai"

describe("github issues > #5501 Incorrect data loading from JSON string for column type 'simple-json'", () => {
    let connections: DataSource[]
    before(async () => {
        connections = await createTestingConnections({
            enabledDrivers: ["mysql", "mariadb"],
            entities: [Post],
            schemaCreate: true,
            dropSchema: true,
        })
    })
    beforeEach(() => reloadTestingDatabases(connections))
    after(() => closeTestingConnections(connections))

    it("should correctly store simple-json field", () =>
        Promise.all(
            connections.map(async (connection) => {
                let id = 0
                const runTestCase = async (
                    input: any,
                    expected: any,
                    message: string,
                ) => {
                    id++

                    await connection
                        .getRepository(Post)
                        .save({ id, jsonField: input })

                    const actual = (await connection
                        .createQueryBuilder()
                        .from("Post", "post")
                        .select("post.jsonField", "json")
                        .where("post.id = :id", { id })
                        .getRawOne())!.json

                    expect(actual).to.be.equal(expected, message)
                }

                await runTestCase(
                    "hello world",
                    '"hello world"',
                    "normal string",
                )
                await runTestCase("", '""', "empty string")
                await runTestCase(
                    "null",
                    '"null"',
                    "string containing the word null",
                )
                await runTestCase(
                    { key: "value" },
                    '{"key":"value"}',
                    "object containing a key and string value",
                )
                await runTestCase(
                    ["hello"],
                    '["hello"]',
                    "array containing a string",
                )
                await runTestCase(null, null, "a null object value")
                await runTestCase(1, "1", "the real number 1")
                await runTestCase(0.3, "0.3", "the number 0.3")
                await runTestCase(true, "true", "the boolean value true")
                await runTestCase(
                    [
                        { hello: "earth", planet: true },
                        { hello: "moon", planet: false },
                    ],
                    '[{"hello":"earth","planet":true},{"hello":"moon","planet":false}]',
                    "a complex object example",
                )
            }),
        ))

    it("should correctly retrieve simple-json field", () =>
        Promise.all(
            connections.map(async (connection) => {
                let id = 0
                const runTestCase = async (
                    input: string | null,
                    expected: any,
                    message: string,
                ) => {
                    id++
                    await connection
                        .createQueryBuilder()
                        .insert()
                        .into(Post)
                        .values({ id, jsonField: () => ":field" } as any) // A bit of a hack to get the raw value inserting
                        .setParameter("field", input)
                        .execute()

                    const actual = (await connection
                        .getRepository(Post)
                        .findOne({ where: { id } }))!.jsonField

                    expect(actual).to.be.eql(expected, message)
                }

                await runTestCase(
                    '"hello world"',
                    "hello world",
                    "normal string",
                )
                await runTestCase('""', "", "empty string")
                await runTestCase(
                    '"null"',
                    "null",
                    "string containing the word null",
                )
                await runTestCase(
                    '{"key":"value"}',
                    { key: "value" },
                    "object containing a key and string value",
                )
                await runTestCase(
                    '["hello"]',
                    ["hello"],
                    "array containing a string",
                )
                await runTestCase(null, null, "a null object value")
                await runTestCase("1", 1, "the real number 1")
                await runTestCase("0.3", 0.3, "the number 0.3")
                await runTestCase("true", true, "the boolean value true")
                await runTestCase(
                    '[{"hello":"earth","planet":true},{"hello":"moon","planet":false}]',
                    [
                        { hello: "earth", planet: true },
                        { hello: "moon", planet: false },
                    ],
                    "a complex object example",
                )
            }),
        ))

    it("should throw an error when the data in the database is invalid", () =>
        Promise.all(
            connections.map(async (connection) => {
                const insert = (id: number, value: string | null) =>
                    connection
                        .createQueryBuilder()
                        .insert()
                        .into(Post)
                        .values({ id, jsonField: () => ":field" } as any) // A bit of a hack to get the raw value inserting
                        .setParameter("field", value)
                        .execute()

                // This was the likely data within the database in #4440
                // This will happen if you've tried to manually insert the data in ways where
                // we aren't expecting you to - like switching the column type to a text &
                // trying to push a value into it that is an object.
                await insert(1, "[object Object]")

                const repo = connection.getRepository(Post)

                const getJson = async (id: number) =>
                    (await repo.findOne({ where: { id } }))!.jsonField

                await expect(getJson(1)).to.be.rejected
            }),
        ))
})
