import mockAxios from "jest-mock-axios"
import { Dcomm, DcommCore } from "../src"
import { DVMAPI } from "../src/apis/dvm/api"
import { AdminAPI } from "../src/apis/admin/api"
import { HealthAPI } from "../src/apis/health/api"
import { InfoAPI } from "../src/apis/info/api"
import { KeystoreAPI } from "../src/apis/keystore/api"
import { MetricsAPI } from "../src/apis/metrics/api"
import { AuthorityVMAPI } from "../src/apis/authorityvm/api"
import { TestAPI } from "./testlib"
import { AxiosRequestConfig } from "axios"
import { HttpResponse } from "jest-mock-axios/dist/lib/mock-axios-types"

describe("Dcomm", (): void => {
  const blockchainID: string =
    "6h2s5de1VC65meajE1L2PjvZ1MXvHc3F6eqPCGKuDt4MxiweF"
  let host: string = "127.0.0.1"
  const port: number = 9650
  const networkID: number = 1337
  let protocol: string = "https"
  let dcomm: Dcomm
  let dcommCore: DcommCore
  const api: string = "api.dcm.network"
  const url: string = "https://api.dcm.network:9650"
  const encrypted: string = "https"
  const skipinit: boolean = true
  beforeAll((): void => {
    dcomm = new Dcomm(
      host,
      port,
      protocol,
      networkID,
      undefined,
      undefined,
      undefined,
      skipinit
    )
    dcomm.addAPI("admin", AdminAPI)
    dcomm.addAPI("astchain", DVMAPI, "/ext/subnet/dvm", blockchainID)
    dcomm.addAPI("health", HealthAPI)
    dcomm.addAPI("info", InfoAPI)
    dcomm.addAPI("keystore", KeystoreAPI)
    dcomm.addAPI("metrics", MetricsAPI)
    dcomm.addAPI("athchain", AuthorityVMAPI)
  })
  test("Remove special characters", (): void => {
    host = "a&&&&p#i,.@a+v(a)x$.~n%e't:w*o?r<k>"
    protocol = "h@t&@&@t#p+s()$"
    dcomm = new Dcomm(host, port, protocol, networkID)
    expect(dcomm.getHost()).toBe(api)
    expect(dcomm.getProtocol()).toBe(encrypted)
    expect(dcomm.getURL()).toBe(url)
    dcommCore = new DcommCore(host, port, protocol)
    expect(dcommCore.getHost()).toBe(api)
    expect(dcommCore.getProtocol()).toBe(encrypted)
    expect(dcommCore.getURL()).toBe(url)
  })
  test("Can specify base endpoint", (): void => {
    dcomm = new Dcomm()
    dcomm.setAddress(api, port, encrypted, "rpc")
    dcomm.setNetworkID(networkID)
    expect(dcomm.getHost()).toBe(api)
    expect(dcomm.getProtocol()).toBe(encrypted)
    expect(dcomm.getPort()).toBe(port)
    expect(dcomm.getBaseEndpoint()).toBe("rpc")
    expect(dcomm.getURL()).toBe(`${url}/rpc`)
    expect(dcomm.getNetworkID()).toBe(networkID)
  })
  test("Can initialize without port", (): void => {
    protocol = encrypted
    host = api
    dcomm = new Dcomm(host, undefined, protocol, networkID)
    expect(dcomm.getPort()).toBe(undefined)
    expect(dcomm.getURL()).toBe(`${protocol}://${api}`)
    dcommCore = new DcommCore(host, undefined, protocol)
    expect(dcommCore.getPort()).toBe(undefined)
    expect(dcommCore.getURL()).toBe(`${protocol}://${api}`)
  })
  test("Can initialize with port", (): void => {
    protocol = encrypted
    dcomm = new Dcomm(host, port, protocol, networkID)
    expect(dcomm.getIP()).toBe(host)
    expect(dcomm.getHost()).toBe(host)
    expect(dcomm.getPort()).toBe(port)
    expect(dcomm.getProtocol()).toBe(protocol)
    expect(dcomm.getURL()).toBe(`${protocol}://${host}:${port}`)
    expect(dcomm.getNetworkID()).toBe(1337)
    expect(dcomm.getHeaders()).toStrictEqual({})
    dcomm.setNetworkID(50)
    expect(dcomm.getNetworkID()).toBe(50)
    dcomm.setNetworkID(12345)
    expect(dcomm.getNetworkID()).toBe(12345)
  })

  test("Endpoints correct", (): void => {
    expect(dcomm.Admin()).not.toBeInstanceOf(DVMAPI)
    expect(dcomm.Admin()).toBeInstanceOf(AdminAPI)

    expect(dcomm.ASTChain()).not.toBeInstanceOf(AdminAPI)
    expect(dcomm.ASTChain()).toBeInstanceOf(DVMAPI)

    expect(dcomm.Health()).not.toBeInstanceOf(KeystoreAPI)
    expect(dcomm.Health()).toBeInstanceOf(HealthAPI)

    expect(dcomm.Info()).not.toBeInstanceOf(KeystoreAPI)
    expect(dcomm.Info()).toBeInstanceOf(InfoAPI)

    expect(dcomm.ATHChain()).not.toBeInstanceOf(KeystoreAPI)
    expect(dcomm.ATHChain()).toBeInstanceOf(AuthorityVMAPI)

    expect(dcomm.NodeKeys()).not.toBeInstanceOf(AuthorityVMAPI)
    expect(dcomm.NodeKeys()).toBeInstanceOf(KeystoreAPI)

    expect(dcomm.Metrics()).not.toBeInstanceOf(KeystoreAPI)
    expect(dcomm.Metrics()).toBeInstanceOf(MetricsAPI)

    expect(dcomm.Admin().getRPCID()).toBe(1)
    expect(dcomm.ASTChain().getRPCID()).toBe(1)
    expect(dcomm.ATHChain().getRPCID()).toBe(1)
    expect(dcomm.NodeKeys().getRPCID()).toBe(1)
  })

  test("Create new API", (): void => {
    dcomm.addAPI("dvm2", DVMAPI)
    expect(dcomm.api("dvm2")).toBeInstanceOf(DVMAPI)

    dcomm.addAPI("keystore2", KeystoreAPI, "/ext/keystore2")
    expect(dcomm.api("keystore2")).toBeInstanceOf(KeystoreAPI)

    dcomm.api("keystore2").setBaseURL("/ext/keystore3")
    expect(dcomm.api("keystore2").getBaseURL()).toBe("/ext/keystore3")

    expect(dcomm.api("keystore2").getDB()).toHaveProperty("namespace")
  })

  test("Customize headers", (): void => {
    dcomm.setHeader("AST-Custom-Header", "example")
    dcomm.setHeader("AST-Foo", "Foo")
    dcomm.setHeader("AST-Bar", "Bar")
    expect(dcomm.getHeaders()).toStrictEqual({
      "AST-Custom-Header": "example",
      "AST-Foo": "Foo",
      "AST-Bar": "Bar"
    })
    dcomm.removeHeader("AST-Foo")
    expect(dcomm.getHeaders()).toStrictEqual({
      "AST-Custom-Header": "example",
      "AST-Bar": "Bar"
    })
    dcomm.removeAllHeaders()
    expect(dcomm.getHeaders()).toStrictEqual({})
  })

  test("Customize request config", (): void => {
    expect(dcomm.getRequestConfig()).toStrictEqual({})
    dcomm.setRequestConfig("withCredentials", true)
    dcomm.setRequestConfig("withFoo", "Foo")
    dcomm.setRequestConfig("withBar", "Bar")
    expect(dcomm.getRequestConfig()).toStrictEqual({
      withCredentials: true,
      withFoo: "Foo",
      withBar: "Bar"
    })
    dcomm.removeRequestConfig("withFoo")
    expect(dcomm.getRequestConfig()).toStrictEqual({
      withCredentials: true,
      withBar: "Bar"
    })
    dcomm.removeAllRequestConfigs()
    expect(dcomm.getRequestConfig()).toStrictEqual({})
  })
})

describe("HTTP Operations", (): void => {
  const host: string = "127.0.0.1"
  const port: number = 8080
  const protocol: string = "http"
  const path: string = "/ext/testingrequests"
  let dcomm: Dcomm
  beforeAll((): void => {
    dcomm = new Dcomm(
      host,
      port,
      protocol,
      12345,
      undefined,
      undefined,
      undefined,
      true
    )
    dcomm.addAPI("testingrequests", TestAPI, path)
  })

  afterEach((): void => {
    mockAxios.reset()
  })

  test("GET works", async (): Promise<void> => {
    const input: string = "TestGET"
    const api: TestAPI = dcomm.api("testingrequests")
    const result: Promise<object> = api.TestGET(input, `/${input}`)
    const payload: object = {
      result: {
        output: input
      }
    }
    const responseObj: HttpResponse = {
      data: payload
    }
    mockAxios.mockResponse(responseObj)
    const response: any = await result
    expect(mockAxios.request).toHaveBeenCalledTimes(1)
    expect(response.output).toBe(input)
  })

  test("DELETE works", async (): Promise<void> => {
    const input: string = "TestDELETE"
    const api: TestAPI = dcomm.api("testingrequests")
    const axiosConfig: AxiosRequestConfig = {
      baseURL: `${protocol}://${host}:${port}`,
      responseType: "text"
    }
    const result: Promise<object> = api.TestDELETE(
      input,
      `/${input}`,
      axiosConfig
    )
    const payload: object = {
      result: {
        output: input
      }
    }
    const responseObj: HttpResponse = {
      data: payload
    }
    mockAxios.mockResponse(responseObj)
    const response: any = await result
    expect(mockAxios.request).toHaveBeenCalledTimes(1)
    expect(response.output).toBe(input)
  })

  test("POST works", async (): Promise<void> => {
    const input: string = "TestPOST"
    const api: TestAPI = dcomm.api("testingrequests")
    const result: Promise<object> = api.TestPOST(input, `/${input}`)
    const payload: object = {
      result: {
        output: input
      }
    }
    const responseObj: HttpResponse = {
      data: payload
    }
    mockAxios.mockResponse(responseObj)
    const response: any = await result
    expect(mockAxios.request).toHaveBeenCalledTimes(1)
    expect(response.output).toBe(input)
  })

  test("PUT works", async (): Promise<void> => {
    const input: string = "TestPUT"
    const api: TestAPI = dcomm.api("testingrequests")
    const result: Promise<object> = api.TestPUT(input, `/${input}`)
    const payload: object = {
      result: {
        output: input
      }
    }
    const responseObj: HttpResponse = {
      data: payload
    }
    mockAxios.mockResponse(responseObj)
    const response: any = await result
    expect(mockAxios.request).toHaveBeenCalledTimes(1)
    expect(response.output).toBe(input)
  })

  test("PATCH works", async (): Promise<void> => {
    const input: string = "TestPATCH"
    const api: TestAPI = dcomm.api("testingrequests")
    const result: Promise<object> = api.TestPATCH(input, `/${input}`)
    const payload: object = {
      result: {
        output: input
      }
    }
    const responseObj: HttpResponse = {
      data: payload
    }
    mockAxios.mockResponse(responseObj)
    const response: any = await result
    expect(mockAxios.request).toHaveBeenCalledTimes(1)
    expect(response.output).toBe(input)
  })
})
