// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import XCTest

@testable import FirebaseAILogic

@available(iOS 15.0, macOS 12.0, macCatalyst 15.0, tvOS 15.0, watchOS 8.0, *)
final class JSONValueTests: XCTestCase {
  let decoder = JSONDecoder()
  let encoder = JSONEncoder()

  let numberKey = "pi"
  let numberValue = 3.14159
  let numberValueEncoded = "3.14159"
  let stringKey = "hello"
  let stringValue = "Hello, world!"

  override func setUp() {
    encoder.outputFormatting = .sortedKeys
  }

  func testDecodeNull() throws {
    let jsonData = try XCTUnwrap("null".data(using: .utf8))

    let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))

    XCTAssertEqual(jsonObject, .null)
  }

  func testDecodeNumber() throws {
    let jsonData = try XCTUnwrap("\(numberValue)".data(using: .utf8))

    let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))

    XCTAssertEqual(jsonObject, .number(numberValue))
  }

  func testDecodeString() throws {
    let jsonData = try XCTUnwrap("\"\(stringValue)\"".data(using: .utf8))

    let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))

    XCTAssertEqual(jsonObject, .string(stringValue))
  }

  func testDecodeBool() throws {
    let expectedBool = true
    let jsonData = try XCTUnwrap("\(expectedBool)".data(using: .utf8))

    let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))

    XCTAssertEqual(jsonObject, .bool(expectedBool))
  }

  func testDecodeObject() throws {
    let expectedObject: JSONObject = [
      numberKey: .number(numberValue),
      stringKey: .string(stringValue),
    ]
    let json = """
    {
      "\(numberKey)": \(numberValue),
      "\(stringKey)": "\(stringValue)"
    }
    """
    let jsonData = try XCTUnwrap(json.data(using: .utf8))

    let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))

    XCTAssertEqual(jsonObject, .object(expectedObject))
  }

  func testDecodeArray() throws {
    let expectedArray: [JSONValue] = [.null, .number(numberValue)]
    let jsonData = try XCTUnwrap("[ null, \(numberValue) ]".data(using: .utf8))

    let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))

    XCTAssertEqual(jsonObject, .array(expectedArray))
  }

  func testEncodeNull() throws {
    let jsonData = try encoder.encode(JSONValue.null)

    let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
    XCTAssertEqual(json, "null")
  }

  func testDecodeNestedObject() throws {
    let nestedObject: JSONObject = [
      "nestedKey": .string("nestedValue"),
    ]
    let expectedObject: JSONObject = [
      "numberKey": .number(numberValue),
      "objectKey": .object(nestedObject),
    ]
    let json = """
    {
      "numberKey": \(numberValue),
      "objectKey": {
        "nestedKey": "nestedValue"
      }
    }
    """
    let jsonData = try XCTUnwrap(json.data(using: .utf8))

    let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))

    XCTAssertEqual(jsonObject, .object(expectedObject))
  }

  func testDecodeNestedArray() throws {
    let nestedArray: [JSONValue] = [.string("a"), .string("b")]
    let expectedObject: JSONObject = [
      "numberKey": .number(numberValue),
      "arrayKey": .array(nestedArray),
    ]
    let json = """
    {
      "numberKey": \(numberValue),
      "arrayKey": ["a", "b"]
    }
    """
    let jsonData = try XCTUnwrap(json.data(using: .utf8))

    let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))

    XCTAssertEqual(jsonObject, .object(expectedObject))
  }

  func testEncodeNumber() throws {
    let jsonData = try encoder.encode(JSONValue.number(numberValue))

    let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
    XCTAssertEqual(json, "\(numberValue)")
  }

  func testEncodeString() throws {
    let jsonData = try encoder.encode(JSONValue.string(stringValue))

    let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
    XCTAssertEqual(json, "\"\(stringValue)\"")
  }

  func testEncodeBool() throws {
    let boolValue = true

    let jsonData = try encoder.encode(JSONValue.bool(boolValue))

    let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
    XCTAssertEqual(json, "\(boolValue)")
  }

  func testEncodeObject() throws {
    let objectValue: JSONObject = [
      numberKey: .number(numberValue),
      stringKey: .string(stringValue),
    ]

    let jsonData = try encoder.encode(JSONValue.object(objectValue))

    let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
    XCTAssertEqual(
      json,
      "{\"\(stringKey)\":\"\(stringValue)\",\"\(numberKey)\":\(numberValueEncoded)}"
    )
  }

  func testEncodeArray() throws {
    let arrayValue: [JSONValue] = [.null, .number(numberValue)]

    let jsonData = try encoder.encode(JSONValue.array(arrayValue))

    let json = try XCTUnwrap(String(data: jsonData, encoding: .utf8))
    XCTAssertEqual(json, "[null,\(numberValueEncoded)]")
  }

  func testEncodeNestedObject() throws {
    let nestedObject: JSONObject = [
      "nestedKey": .string("nestedValue"),
    ]
    let objectValue: JSONObject = [
      "numberKey": .number(numberValue),
      "objectKey": .object(nestedObject),
    ]

    let jsonData = try encoder.encode(JSONValue.object(objectValue))
    let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))
    XCTAssertEqual(jsonObject, .object(objectValue))
  }

  func testEncodeNestedArray() throws {
    let nestedArray: [JSONValue] = [.string("a"), .string("b")]
    let objectValue: JSONObject = [
      "numberKey": .number(numberValue),
      "arrayKey": .array(nestedArray),
    ]

    let jsonData = try encoder.encode(JSONValue.object(objectValue))
    let jsonObject = try XCTUnwrap(decoder.decode(JSONValue.self, from: jsonData))
    XCTAssertEqual(jsonObject, .object(objectValue))
  }
}
