/* eslint-env mocha */
import expect from "expect"
import { fromJS } from "immutable"
import { fromJSOrdered } from "core/utils"
import {
  definitions,
  parameterValues,
  contentTypeValues,
  operationScheme,
  specJsonWithResolvedSubtrees,
  producesOptionsFor,
  operationWithMeta,
  parameterWithMeta,
  parameterWithMetaByIdentity,
  parameterInclusionSettingFor,
  consumesOptionsFor,
  taggedOperations
} from "corePlugins/spec/selectors"

import Petstore from "./assets/petstore.json"

  describe("definitions", function(){
    it("should return definitions by default", function(){

      // Given
      const spec = fromJS({
        json: {
          swagger: "2.0",
          definitions: {
            a: {
              type: "string"
            },
            b: {
              type: "string"
            }
          }
        }
      })

      // When
      let res = definitions(spec)

      // Then
      expect(res.toJS()).toEqual({
        a: {
          type: "string"
        },
        b: {
          type: "string"
        }
      })
    })
    it("should return an empty Map when missing definitions", function(){

      // Given
      const spec = fromJS({
        json: {
          swagger: "2.0"
        }
      })

      // When
      let res = definitions(spec)

      // Then
      expect(res.toJS()).toEqual({})
    })
    it("should return an empty Map when given non-object definitions", function(){

      // Given
      const spec = fromJS({
        json: {
          swagger: "2.0",
          definitions: "..."
        }
      })

      // When
      let res = definitions(spec)

      // Then
      expect(res.toJS()).toEqual({})
    })
  })

  describe("parameterValue", function(){

    it("should return Map({}) if no path found", function(){

      // Given
      const spec = fromJS({ })

      // When
      let paramValues = parameterValues(spec, ["/one", "get"])

      // Then
      expect(paramValues.toJS()).toEqual({})

    })

    it("should return a hash of [parameterName]: value", function(){

      // Given
      const spec = fromJS({
        json: {
          paths: {
            "/one": {
              get: {
                parameters: [
                  { name: "one", in: "query", value: 1},
                  { name: "two", in: "query", value: "duos"}
                ]
              }
            }
          }
        }
      })

      // When
      let paramValues = parameterValues(spec, ["/one", "get"])

      // Then
      expect(paramValues.toJS()).toEqual({
        "query.one": 1,
        "query.two": "duos"
      })

    })

  })

  describe("contentTypeValues", function(){
    it("should return { requestContentType, responseContentType } from an operation", function(){
      // Given
      let state = fromJS({
        json: {
          paths: {
            "/one": {
              get: {}
            }
          }
        },
        meta: {
          paths: {
            "/one": {
              get: {
                "consumes_value": "one",
                "produces_value": "two"
              }
            }
          }
        }
      })

      // When
      let contentTypes = contentTypeValues(state, [ "/one", "get" ])
      // Then
      expect(contentTypes.toJS()).toEqual({
        requestContentType: "one",
        responseContentType: "two"
      })
    })

    it("should default to the first `produces` array value if current is not set", function(){
      // Given
      let state = fromJS({
        json: {
          paths: {
            "/one": {
              get: {
                produces: [
                  "application/xml",
                  "application/whatever"
                ]
              }
            }
          }
        },
        meta: {
          paths: {
            "/one": {
              get: {
                "consumes_value": "one"
              }
            }
          }
        }
      })

      // When
      let contentTypes = contentTypeValues(state, [ "/one", "get" ])
      // Then
      expect(contentTypes.toJS()).toEqual({
        requestContentType: "one",
        responseContentType: "application/xml"
      })
    })

    it("should default to `application/json` if a default produces value is not available", function(){
      // Given
      let state = fromJS({
        json: {
          paths: {
            "/one": {
              get: {}
            }
          }
        },
        meta: {
          paths: {
            "/one": {
              get: {
                "consumes_value": "one"
              }
            }
          }
        }
      })

      // When
      let contentTypes = contentTypeValues(state, [ "/one", "get" ])
      // Then
      expect(contentTypes.toJS()).toEqual({
        requestContentType: "one",
        responseContentType: "application/json"
      })
    })

    it("should prioritize consumes value first from an operation", function(){
      // Given
      let state = fromJS({
        json: {
          paths: {
            "/one": {
              get: {
                "parameters": [{
                  "type": "file"
                }],
              }
            }
          }
        },
        meta: {
          paths: {
            "/one": {
              get: {
                "consumes_value": "one",
              }
            }
          }
        }
      })

      // When
      let contentTypes = contentTypeValues(state, [ "/one", "get" ])
      // Then
      expect(contentTypes.toJS().requestContentType).toEqual("one")
    })

    it("should fallback to multipart/form-data if there is no consumes value but there is a file parameter", function(){
      // Given
      let state = fromJS({
        json: {
          paths: {
            "/one": {
              get: {
                "parameters": [{
                  "type": "file"
                }],
              }
            }
          }
        }
      })

      // When
      let contentTypes = contentTypeValues(state, [ "/one", "get" ])
      // Then
      expect(contentTypes.toJS().requestContentType).toEqual("multipart/form-data")
    })

    it("should fallback to application/x-www-form-urlencoded if there is no consumes value, no file parameter, but there is a formData parameter", function(){
      // Given
      let state = fromJS({
        json: {
          paths: {
            "/one": {
              get: {
                "parameters": [{
                  "type": "formData"
                }],
              }
            }
          }
        }
      })

      // When
      let contentTypes = contentTypeValues(state, [ "/one", "get" ])
      // Then
      expect(contentTypes.toJS().requestContentType).toEqual("application/x-www-form-urlencoded")
    })

    it("should return nothing, if the operation does not exist", function(){
      // Given
      let state = fromJS({ })

      // When
      let contentTypes = contentTypeValues(state, [ "/one", "get" ])
      // Then
      expect(contentTypes.toJS()).toEqual({
        requestContentType: undefined,
        responseContentType: undefined
      })
    })

  })

  describe("operationScheme", function(){

    it("should return the correct scheme for a remote spec that doesn't specify a scheme", function(){
      // Given
      let state = fromJS({
        url: "https://generator.swagger.io/api/swagger.json",
        json: {
          paths: {
            "/one": {
              get: {
                "consumes_value": "one",
                "produces_value": "two"
              }
            }
          }
        }
      })

      // When
      let scheme = operationScheme(state, ["/one"], "get")
      // Then
      expect(scheme).toEqual("https")
    })

    // it("should be ok, if no operation found", function(){
    //   // Given
    //   let state = fromJS({ })
    //
    //   // When
    //   let contentTypes = contentTypeValues(state, [ "/one", "get" ])
    //   // Then
    //   expect(contentTypes.toJS()).toEqual({
    //     requestContentType: undefined,
    //     responseContentType: undefined
    //   })
    // })

  })

  describe("specJsonWithResolvedSubtrees", function(){

    it("should return a correctly merged tree", function(){
      // Given
      let state = fromJS({
        json: {
          definitions: {
            Asdf: {
              $ref: "#/some/path",
              randomKey: "this should be removed b/c siblings of $refs must be removed, per the specification",
              description: "same for this key"
            },
            Fgsfds: {
              $ref: "#/another/path"
            },
            OtherDef: {
              description: "has no refs"
            }
          }
        },
        resolvedSubtrees: {
          definitions: {
            Asdf: {
              type: "object",
              $$ref: "#/some/path"
            }
          }
        }
      })

      // When
      let result = specJsonWithResolvedSubtrees(state)
      // Then
      expect(result.toJS()).toEqual({
        definitions: {
          Asdf: {
            type: "object",
            $$ref: "#/some/path"
          },
          Fgsfds: {
            $ref: "#/another/path"
          },
          OtherDef: {
            description: "has no refs"
          }
        }
      })
    })
    it("should preserve initial map key ordering", function(){
      // Given
      let state = fromJSOrdered({
        json: Petstore,
        resolvedSubtrees: {
            paths: {
              "/pet/{petId}": {
                post: {
                  tags: [
                    "pet"
                  ],
                  summary: "Updates a pet in the store with form data",
                  description: "",
                  operationId: "updatePetWithForm",
                  consumes: [
                    "application/x-www-form-urlencoded"
                  ],
                  produces: [
                    "application/xml",
                    "application/json"
                  ],
                  parameters: [
                    {
                      name: "petId",
                      "in": "path",
                      description: "ID of pet that needs to be updated",
                      required: true,
                      type: "integer",
                      format: "int64"
                    },
                    {
                      name: "name",
                      "in": "formData",
                      description: "Updated name of the pet",
                      required: false,
                      type: "string"
                    },
                    {
                      name: "status",
                      "in": "formData",
                      description: "Updated status of the pet",
                      required: false,
                      type: "string"
                    }
                  ],
                  responses: {
                    "405": {
                      description: "Invalid input"
                    }
                  },
                  security: [
                    {
                      petstore_auth: [
                        "write:pets",
                        "read:pets"
                      ]
                    }
                  ],
                  __originalOperationId: "updatePetWithForm"
                }
              }
            }
        }
      })

      // When
      let result = specJsonWithResolvedSubtrees(state)

      // Then
      const correctOrder = [
        "/pet",
        "/pet/findByStatus",
        "/pet/findByTags",
        "/pet/{petId}",
        "/pet/{petId}/uploadImage",
        "/store/inventory",
        "/store/order",
        "/store/order/{orderId}",
        "/user",
        "/user/createWithArray",
        "/user/createWithList",
        "/user/login",
        "/user/logout",
        "/user/{username}"
      ]
      expect(state.getIn(["json", "paths"]).keySeq().toJS()).toEqual(correctOrder)
      expect(result.getIn(["paths"]).keySeq().toJS()).toEqual(correctOrder)
    })
  })

  describe("operationWithMeta", function() {
    it("should support merging in {in}.{name} keyed param metadata", function () {
      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                parameters: [
                  {
                    name: "myBody",
                    in: "body"
                  }
                ]
              }
            }
          }
        },
        meta: {
          paths: {
            "/": {
              "get": {
                parameters: {
                  "body.myBody": {
                    value: "abc123"
                  }
                }
              }
            }
          }
        }
      })

      const result = operationWithMeta(state, "/", "get")

      expect(result.toJS()).toEqual({
        parameters: [
          {
            name: "myBody",
            in: "body",
            value: "abc123"
          }
        ]
      })
    })
    it("should support merging in hash-keyed param metadata", function () {
      const bodyParam = fromJS({
        name: "myBody",
        in: "body"
      })

      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                parameters: [
                  bodyParam
                ]
              }
            }
          }
        },
        meta: {
          paths: {
            "/": {
              "get": {
                parameters: {
                  [`body.myBody.hash-${bodyParam.hashCode()}`]: {
                    value: "abc123"
                  }
                }
              }
            }
          }
        }
      })

      const result = operationWithMeta(state, "/", "get")

      expect(result.toJS()).toEqual({
        parameters: [
          {
            name: "myBody",
            in: "body",
            value: "abc123"
          }
        ]
      })
    })
  })
  describe("parameterWithMeta", function() {
    it("should support merging in {in}.{name} keyed param metadata", function () {
      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                parameters: [
                  {
                    name: "myBody",
                    in: "body"
                  }
                ]
              }
            }
          }
        },
        meta: {
          paths: {
            "/": {
              "get": {
                parameters: {
                  "body.myBody": {
                    value: "abc123"
                  }
                }
              }
            }
          }
        }
      })

      const result = parameterWithMeta(state, ["/", "get"], "myBody", "body")

      expect(result.toJS()).toEqual({
        name: "myBody",
        in: "body",
        value: "abc123"
      })
    })
    it("should give best-effort when encountering hash-keyed param metadata", function () {
      const bodyParam = fromJS({
        name: "myBody",
        in: "body"
      })

      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                parameters: [
                  bodyParam
                ]
              }
            }
          }
        },
        meta: {
          paths: {
            "/": {
              "get": {
                parameters: {
                  [`body.myBody.hash-${bodyParam.hashCode()}`]: {
                    value: "abc123"
                  }
                }
              }
            }
          }
        }
      })

      const result = parameterWithMeta(state, ["/", "get"], "myBody", "body")

      expect(result.toJS()).toEqual({
        name: "myBody",
        in: "body",
        value: "abc123"
      })
    })

  })
  describe("parameterWithMetaByIdentity", function() {
    it("should support merging in {in}.{name} keyed param metadata", function () {
      const bodyParam = fromJS({
        name: "myBody",
        in: "body"
      })

      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                parameters: [bodyParam]
              }
            }
          }
        },
        meta: {
          paths: {
            "/": {
              "get": {
                parameters: {
                  "body.myBody": {
                    value: "abc123"
                  }
                }
              }
            }
          }
        }
      })

      const result = parameterWithMetaByIdentity(state, ["/", "get"], bodyParam)

      expect(result.toJS()).toEqual({
        name: "myBody",
        in: "body",
        value: "abc123"
      })
    })
    it("should support merging in hash-keyed param metadata", function () {
      const bodyParam = fromJS({
        name: "myBody",
        in: "body"
      })

      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                parameters: [
                  bodyParam
                ]
              }
            }
          }
        },
        meta: {
          paths: {
            "/": {
              "get": {
                parameters: {
                  [`body.myBody.hash-${bodyParam.hashCode()}`]: {
                    value: "abc123"
                  }
                }
              }
            }
          }
        }
      })

      const result = parameterWithMetaByIdentity(state, ["/", "get"], bodyParam)

      expect(result.toJS()).toEqual({
        name: "myBody",
        in: "body",
        value: "abc123"
      })
    })
  })
  describe("parameterInclusionSettingFor", function() {
    it("should support getting {in}.{name} param inclusion settings", function () {
      const param = fromJS({
        name: "param",
        in: "query",
        allowEmptyValue: true
      })

      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                parameters: [
                  param
                ]
              }
            }
          }
        },
        meta: {
          paths: {
            "/": {
              "get": {
                "parameter_inclusions": {
                  [`query.param`]: true
                }
              }
            }
          }
        }
      })

      const result = parameterInclusionSettingFor(state, ["/", "get"], "param", "query")

      expect(result).toEqual(true)
    })
  })
  describe("producesOptionsFor", function() {
    it("should return an operation produces value", function () {
      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                description: "my operation",
                produces: [
                  "operation/one",
                  "operation/two",
                ]
              }
            }
          }
        }
      })

      const result = producesOptionsFor(state, ["/", "get"])

      expect(result.toJS()).toEqual([
        "operation/one",
        "operation/two",
      ])
    })
    it("should return a path item produces value", function () {
      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                description: "my operation",
                produces: [
                  "path-item/one",
                  "path-item/two",
                ]
              }
            }
          }
        }
      })

      const result = producesOptionsFor(state, ["/", "get"])

      expect(result.toJS()).toEqual([
        "path-item/one",
        "path-item/two",
      ])
    })
    it("should return a global produces value", function () {
      const state = fromJS({
        json: {
          produces: [
            "global/one",
            "global/two",
          ],
          paths: {
            "/": {
              "get": {
                description: "my operation"
              }
            }
          }
        }
      })

      const result = producesOptionsFor(state, ["/", "get"])

      expect(result.toJS()).toEqual([
        "global/one",
        "global/two",
      ])
    })
    it("should favor an operation produces value over a path-item value", function () {
      const state = fromJS({
        json: {
          paths: {
            "/": {
              produces: [
                "path-item/one",
                "path-item/two",
              ],
              "get": {
                description: "my operation",
                produces: [
                  "operation/one",
                  "operation/two",
                ]
              }
            }
          }
        }
      })

      const result = producesOptionsFor(state, ["/", "get"])

      expect(result.toJS()).toEqual([
        "operation/one",
        "operation/two",
      ])
    })
    it("should favor a path-item produces value over a global value", function () {
      const state = fromJS({
        json: {
          produces: [
            "global/one",
            "global/two",
          ],
          paths: {
            "/": {
              produces: [
                "path-item/one",
                "path-item/two",
              ],
              "get": {
                description: "my operation"
              }
            }
          }
        }
      })

      const result = producesOptionsFor(state, ["/", "get"])

      expect(result.toJS()).toEqual([
        "path-item/one",
        "path-item/two",
      ])
    })
  })
  describe("consumesOptionsFor", function() {
    it("should return an operation consumes value", function () {
      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                description: "my operation",
                consumes: [
                  "operation/one",
                  "operation/two",
                ]
              }
            }
          }
        }
      })

      const result = consumesOptionsFor(state, ["/", "get"])

      expect(result.toJS()).toEqual([
        "operation/one",
        "operation/two",
      ])
    })
    it("should return a path item consumes value", function () {
      const state = fromJS({
        json: {
          paths: {
            "/": {
              "get": {
                description: "my operation",
                consumes: [
                  "path-item/one",
                  "path-item/two",
                ]
              }
            }
          }
        }
      })

      const result = consumesOptionsFor(state, ["/", "get"])

      expect(result.toJS()).toEqual([
        "path-item/one",
        "path-item/two",
      ])
    })
    it("should return a global consumes value", function () {
      const state = fromJS({
        json: {
          consumes: [
            "global/one",
            "global/two",
          ],
          paths: {
            "/": {
              "get": {
                description: "my operation"
              }
            }
          }
        }
      })

      const result = consumesOptionsFor(state, ["/", "get"])

      expect(result.toJS()).toEqual([
        "global/one",
        "global/two",
      ])
    })
    it("should favor an operation consumes value over a path-item value", function () {
      const state = fromJS({
        json: {
          paths: {
            "/": {
              consumes: [
                "path-item/one",
                "path-item/two",
              ],
              "get": {
                description: "my operation",
                consumes: [
                  "operation/one",
                  "operation/two",
                ]
              }
            }
          }
        }
      })

      const result = consumesOptionsFor(state, ["/", "get"])

      expect(result.toJS()).toEqual([
        "operation/one",
        "operation/two",
      ])
    })
    it("should favor a path-item consumes value over a global value", function () {
      const state = fromJS({
        json: {
          consumes: [
            "global/one",
            "global/two",
          ],
          paths: {
            "/": {
              consumes: [
                "path-item/one",
                "path-item/two",
              ],
              "get": {
                description: "my operation"
              }
            }
          }
        }
      })

      const result = consumesOptionsFor(state, ["/", "get"])

      expect(result.toJS()).toEqual([
        "path-item/one",
        "path-item/two",
      ])
    })
  })
  describe("taggedOperations", function () {
    it("should return a List of ad-hoc tagged operations", function () {
      const system = {
        getConfigs: () => ({})
      }
      const state = fromJS({
        json: {
          // tags: [
          //   "myTag"
          // ],
          paths: {
            "/": {
              "get": {
                produces: [],
                tags: ["myTag"],
                description: "my operation",
                consumes: [
                  "operation/one",
                  "operation/two",
                ]
              }
            }
          }
        }
      })

      const result = taggedOperations(state)(system)

      const op = state.getIn(["json", "paths", "/", "get"]).toJS()

      expect(result.toJS()).toEqual({
        myTag: {
          tagDetails: undefined,
          operations: [{
            id: "get-/",
            method: "get",
            path: "/",
            operation: op
          }]
        }
      })
    })
    it("should return a List of defined tagged operations", function () {
      const system = {
        getConfigs: () => ({})
      }
      const state = fromJS({
        json: {
          tags: [
            {
              name: "myTag"
            }
          ],
          paths: {
            "/": {
              "get": {
                produces: [],
                tags: ["myTag"],
                description: "my operation",
                consumes: [
                  "operation/one",
                  "operation/two",
                ]
              }
            }
          }
        }
      })

      const result = taggedOperations(state)(system)

      const op = state.getIn(["json", "paths", "/", "get"]).toJS()

      expect(result.toJS()).toEqual({
        myTag: {
          tagDetails: {
            name: "myTag"
          },
          operations: [{
            id: "get-/",
            method: "get",
            path: "/",
            operation: op
          }]
        }
      })
    })
    it("should gracefully handle a malformed global tags array", function () {
      const system = {
        getConfigs: () => ({})
      }
      const state = fromJS({
        json: {
          tags: [null],
          paths: {
            "/": {
              "get": {
                produces: [],
                tags: ["myTag"],
                description: "my operation",
                consumes: [
                  "operation/one",
                  "operation/two",
                ]
              }
            }
          }
        }
      })

      const result = taggedOperations(state)(system)

      const op = state.getIn(["json", "paths", "/", "get"]).toJS()

      expect(result.toJS()).toEqual({
        myTag: {
          tagDetails: undefined,
          operations: [{
            id: "get-/",
            method: "get",
            path: "/",
            operation: op
          }]
        }
      })
    })
    it("should gracefully handle a non-array global tags entry", function () {
      const system = {
        getConfigs: () => ({})
      }
      const state = fromJS({
        json: {
          tags: "asdf",
          paths: {
            "/": {
              "get": {
                produces: [],
                tags: ["myTag"],
                description: "my operation",
                consumes: [
                  "operation/one",
                  "operation/two",
                ]
              }
            }
          }
        }
      })

      const result = taggedOperations(state)(system)

      const op = state.getIn(["json", "paths", "/", "get"]).toJS()

      expect(result.toJS()).toEqual({
        myTag: {
          tagDetails: undefined,
          operations: [{
            id: "get-/",
            method: "get",
            path: "/",
            operation: op
          }]
        }
      })
    })
  })
