import { test } from '@japa/runner'
import { Environment, IEnvironment, Kind as EnvironmentKind } from '../../../src/models/Environment.js'
import { Server, Kind as ServerKind } from '../../../src/models/Server.js'
import { Property, Kind as PropertyKind, PropertyType } from '../../../src/models/Property.js'
import { RequestAuthorization, Kind as RequestAuthorizationKind } from '../../../src/models/RequestAuthorization.js'
import { ThingKind } from '../../../src/models/kinds.js'

test.group('Models > Environment > Initialization', () => {
  test('initializes a default environment', ({ assert }) => {
    const result = new Environment()
    assert.equal(result.kind, EnvironmentKind, 'sets the kind property')
    assert.typeOf(result.key, 'string', 'sets the key property')

    const { info } = result
    assert.typeOf(info, 'EventTarget', 'sets the default info property')
    assert.equal(info.kind, ThingKind, 'sets the info.kind property')
    assert.equal(info.name, '', 'sets the empty info.name property')
  })
})

test.group('Models > Environment > From schema initialization', (group) => {
  let base: IEnvironment
  group.each.setup(() => {
    base = {
      kind: EnvironmentKind,
      key: 'test1234',
      info: {
        kind: ThingKind,
        name: 'An env',
      },
      variables: [
        {
          kind: PropertyKind,
          name: 'a var',
          type: 'string',
          value: '',
        },
      ],
      encapsulated: true,
      server: {
        kind: ServerKind,
        uri: 'api.com',
        basePath: '/',
        description: 'a server',
        protocol: 'https',
      },
    }
  })

  test('sets the info', ({ assert }) => {
    const init: IEnvironment = {
      ...base,
      ...{
        info: {
          kind: ThingKind,
          name: 'Test env',
          description: 'Env description',
          version: '1.2.3',
        },
      },
    }
    const result = new Environment(init)
    const { info } = result
    assert.equal(info.kind, ThingKind, 'sets the info.kind property')
    assert.equal(info.name, 'Test env', 'sets the info.name property')
    assert.equal(info.description, 'Env description', 'sets the info.description property')
    assert.equal(info.version, '1.2.3', 'sets the info.version property')
  })

  test('sets the server', ({ assert }) => {
    const result = new Environment(base)
    const { server } = result

    assert.equal(server!.uri, 'api.com')
    assert.equal(server!.basePath, '/')
    assert.equal(server!.description, 'a server')
    assert.equal(server!.protocol, 'https')
  })

  test('sets the passed key', ({ assert }) => {
    const result = new Environment(base)
    assert.equal(result.key, 'test1234')
  })

  test('sets a new key when the passed key is missing', ({ assert }) => {
    const init: IEnvironment = { ...base }
    // @ts-expect-error Used in testing
    delete init.key
    const result = new Environment(init)
    assert.typeOf(result.key, 'string')
  })

  test('sets encapsulated to false by default', ({ assert }) => {
    const init: IEnvironment = { ...base }
    delete init.encapsulated
    const result = new Environment(init)
    assert.isFalse(result.encapsulated)
  })

  test('sets the variables', ({ assert }) => {
    const result = new Environment(base)
    assert.lengthOf(result.variables, 1, 'has the variables')
    const [var1] = result.variables
    assert.equal(var1.kind, PropertyKind)
    assert.equal(var1.name, 'a var')
    assert.equal(var1.type, 'string')
    assert.equal(var1.value, '')
  })

  test('sets the default variables', ({ assert }) => {
    const init: IEnvironment = { ...base }
    // @ts-expect-error Used in testing
    delete init.variables
    const result = new Environment(init)
    assert.deepEqual(result.variables, [])
  })
})

test.group('Models > Environment > From JSON string initialization', () => {
  test('restores the data from JSON string', ({ assert }) => {
    const env = new Environment()
    env.info.name = 'an environment'
    env.addVariable('test', 123)
    env.addServer('https://api.com')
    const str = JSON.stringify(env)

    const result = new Environment(str)

    assert.equal(result.key, env.key, 'restores the key')
    assert.equal(result.info.name, 'an environment', 'restores the info object')
    assert.lengthOf(result.variables, 1, 'restores the variables')
    assert.equal(result.server!.uri, 'https://api.com', 'restores the server')
  })

  test('throws when invalid folder object', ({ assert }) => {
    const env = new Environment()
    env.info.name = 'an environment'
    const schema = env.toJSON()
    // @ts-expect-error Used in testing
    delete schema.kind
    const str = JSON.stringify(schema)

    assert.throws(() => {
      new Environment(str)
    })
  })
})

test.group('Models > Environment > Environment.fromName()', () => {
  test('adds the name', ({ assert }) => {
    const result = Environment.fromName('an env')
    assert.equal(result.info.name, 'an env')
  })

  test('generates the key', ({ assert }) => {
    const result = Environment.fromName('an env')
    assert.typeOf(result.key, 'string')
  })

  test('adds empty variables', ({ assert }) => {
    const result = Environment.fromName('an env')
    assert.deepEqual(result.variables, [])
  })
})

test.group('Models > Environment > Environment.fromLegacyVariables()', () => {
  test('adds the name', ({ assert }) => {
    const result = Environment.fromLegacyVariables('an env', [])
    assert.equal(result.info.name, 'an env')
  })

  test('generates the key', ({ assert }) => {
    const result = Environment.fromLegacyVariables('an env', [])
    assert.typeOf(result.key, 'string')
  })

  test('adds the variable', ({ assert }) => {
    const result = Environment.fromLegacyVariables('an env', [
      {
        environment: 'any',
        name: 'n1',
        value: 'v1',
      },
    ])
    assert.lengthOf(result.variables, 1, 'has the variable')
    const [v1] = result.variables
    assert.equal(v1.kind, PropertyKind)
    assert.equal(v1.name, 'n1')
    assert.equal(v1.value, 'v1')
    assert.equal(v1.type, 'string')
  })
})

test.group('Models > Environment > new()', () => {
  test('adds default info', ({ assert }) => {
    const env = Environment.fromName('a')
    const schema = env.toJSON()
    // @ts-expect-error Used in testing
    delete schema.info
    env.new(schema)

    assert.typeOf(env.info, 'EventTarget', 'sets the info object')
    assert.equal(env.info.name, '', 'sets the empty name')
  })

  test('removes the server when not defined in the schema', ({ assert }) => {
    const env = Environment.fromName('a')
    env.server = Server.fromUri('https://api.com')
    const schema = env.toJSON()
    delete schema.server
    env.new(schema)

    assert.isUndefined(env.server, 'sets the server')
  })

  test('sets the passed server', ({ assert }) => {
    const env = Environment.fromName('a')
    const schema = env.toJSON()
    schema.server = {
      kind: ServerKind,
      uri: 'abc',
    }
    env.new(schema)

    assert.typeOf(env.server, 'object', 'sets the server')
    assert.equal(env.server!.uri, 'abc')
  })

  test('clears the variables when not defined in the schema', ({ assert }) => {
    const env = Environment.fromName('a')
    env.addVariable('a', 'b')
    const schema = env.toJSON()
    // @ts-expect-error Used in testing
    delete schema.variables
    env.new(schema)

    assert.deepEqual(env.variables, [])
  })

  test('sets the passed variables', ({ assert }) => {
    const env = Environment.fromName('a')
    const schema = env.toJSON()
    schema.variables = [
      {
        kind: PropertyKind,
        name: 'a',
        value: 'b',
        type: 'string',
      },
    ]
    env.new(schema)

    assert.lengthOf(env.variables, 1, 'has a variable')
    assert.equal(env.variables[0].name, 'a')
  })

  test('re-sets the kind', ({ assert }) => {
    const env = Environment.fromName('a')
    env.kind = 'test'
    const schema = env.toJSON()
    env.new(schema)

    assert.equal(env.kind, EnvironmentKind)
  })

  test('sets the key', ({ assert }) => {
    const env = Environment.fromName('a')
    const schema = env.toJSON()
    schema.key = '1234'
    env.new(schema)

    assert.equal(env.key, '1234')
  })

  test('generates a new key', ({ assert }) => {
    const env = Environment.fromName('a')
    const schema = env.toJSON()
    // @ts-expect-error Used in testing
    delete schema.key
    env.new(schema)

    assert.typeOf(env.key, 'string')
  })

  test('sets default "encapsulated"', ({ assert }) => {
    const env = Environment.fromName('a')
    const schema = env.toJSON()
    delete schema.encapsulated
    env.new(schema)

    assert.isFalse(env.encapsulated)
  })

  test('sets the "encapsulated"', ({ assert }) => {
    const env = Environment.fromName('a')
    const schema = env.toJSON()
    schema.encapsulated = true
    env.new(schema)

    assert.isTrue(env.encapsulated)
  })

  test('sets the authorization', ({ assert }) => {
    const instance = new Environment()
    const schema = instance.toJSON()
    schema.authorization = [
      {
        kind: RequestAuthorizationKind,
        enabled: true,
        type: 'oauth 2',
        valid: true,
        config: {},
      },
    ]
    instance.new(schema)

    assert.typeOf(instance.authorization, 'array')
    assert.equal(instance.authorization![0].type, 'oauth 2')
  })

  test('sets the authorization to undefined when missing', ({ assert }) => {
    const instance = new Environment()
    const schema = instance.toJSON()
    schema.authorization = [
      {
        kind: RequestAuthorizationKind,
        enabled: true,
        type: 'oauth 2',
        valid: true,
        config: {},
      },
    ]
    instance.new(schema)
    delete schema.authorization
    instance.new(schema)

    assert.isUndefined(instance.authorization)
  })
})

test.group('Models > Environment > toJSON()', (group) => {
  let base: IEnvironment
  group.each.setup(() => {
    base = {
      kind: EnvironmentKind,
      key: 'test1234',
      info: {
        kind: ThingKind,
        name: 'An env',
      },
      variables: [
        {
          kind: PropertyKind,
          name: 'a var',
          type: 'string',
          value: '',
        },
      ],
      encapsulated: true,
      server: {
        kind: ServerKind,
        uri: 'api.com',
        basePath: '/',
        description: 'a server',
        protocol: 'https',
      },
    }
  })

  test('sets the info', ({ assert }) => {
    const init: IEnvironment = {
      ...base,
      ...{
        info: {
          kind: ThingKind,
          name: 'Test env',
          description: 'Env description',
          version: '1.2.3',
        },
      },
    }
    const env = new Environment(init)
    const result = env.toJSON()
    const { info } = result
    assert.equal(info.kind, ThingKind, 'sets the info.kind property')
    assert.equal(info.name, 'Test env', 'sets the info.name property')
    assert.equal(info.description, 'Env description', 'sets the info.description property')
    assert.equal(info.version, '1.2.3', 'sets the info.version property')
  })

  test('serializes the key', ({ assert }) => {
    const init: IEnvironment = { ...base }
    const env = new Environment(init)
    const result = env.toJSON()
    assert.equal(result.key, init.key)
  })

  test('serializes the encapsulated', ({ assert }) => {
    const init: IEnvironment = { ...base }
    const env = new Environment(init)
    const result = env.toJSON()
    assert.isTrue(result.encapsulated)
  })

  test('serializes the server', ({ assert }) => {
    const init: IEnvironment = { ...base }
    const env = new Environment(init)
    const result = env.toJSON()
    const { server } = result
    assert.equal(server!.kind, ServerKind)
    assert.equal(server!.uri, 'api.com')
    assert.equal(server!.basePath, '/')
    assert.equal(server!.description, 'a server')
    assert.equal(server!.protocol, 'https')
  })

  test('serializes variables', ({ assert }) => {
    const init: IEnvironment = { ...base }
    const env = new Environment(init)
    const result = env.toJSON()
    const { variables } = result
    assert.lengthOf(variables, 1, 'has the variable')
    const [v1] = variables

    assert.equal(v1.name, 'a var')
    assert.equal(v1.type, 'string')
    assert.equal(v1.value, '')
  })

  test('sets the authorization', ({ assert }) => {
    const instance = new Environment()
    instance.authorization = [
      new RequestAuthorization({
        kind: RequestAuthorizationKind,
        enabled: true,
        type: 'oauth 2',
        valid: true,
        config: {},
      }),
    ]
    const result = instance.toJSON()

    assert.typeOf(result.authorization, 'array')
    assert.equal(result.authorization![0].type, 'oauth 2')
  })

  test('does not set the authorization when missing', ({ assert }) => {
    const instance = new Environment()
    const result = instance.toJSON()
    assert.isUndefined(result.authorization)
  })
})

test.group('Models > Environment > addVariable()', () => {
  test('adds variable by name and value', ({ assert }) => {
    const env = new Environment()
    const result = env.addVariable('a', 'b')

    assert.typeOf(result, 'object', 'returns the created property')
    assert.equal(result.name, 'a', 'the created property has the name')
    assert.equal(result.value, 'b', 'the created property has the value')
    assert.equal(result.type, 'string' as PropertyType, 'the created property has the type')
    assert.lengthOf(env.variables, 1, 'has the variable')
    assert.deepEqual(env.variables[0], result, 'inserted variable is the created property')
  })

  test('adds variable by property schema', ({ assert }) => {
    const env = new Environment()
    const prop = Property.Integer('a', 1)
    const result = env.addVariable(prop.toJSON())

    assert.typeOf(result, 'object', 'returns the created property')
    assert.equal(result.name, 'a', 'the created property has the name')
    assert.equal(result.value, 1, 'the created property has the value')
    assert.equal(result.type, 'integer' as PropertyType, 'the created property has the type')
    assert.lengthOf(env.variables, 1, 'has the variable')
    assert.deepEqual(env.variables[0], prop, 'inserted variable is the created property')
  })

  test('creates variables array when missing', ({ assert }) => {
    const env = new Environment()
    // @ts-expect-error Used in testing
    delete env.variables
    env.addVariable('a', 'b')
    assert.typeOf(env.variables, 'array')
    assert.lengthOf(env.variables, 1)
  })

  test('should set default variable type to string', ({ assert }) => {
    const env = new Environment()
    const property = env.addVariable('test', 'value')
    assert.equal(property.type, 'string' as PropertyType)
  })
})

test.group('Models > Environment > getServer()', () => {
  test('returns undefined when no server', ({ assert }) => {
    const env = new Environment()
    const result = env.getServer()
    assert.isUndefined(result)
  })

  test('returns the default server when forced', ({ assert }) => {
    const env = new Environment()
    const result = env.getServer(true)!
    assert.typeOf(result, 'object', 'has the server')
    assert.equal(result.readUri(), '', 'has the default URI')
  })

  test('returns existing server', ({ assert }) => {
    const env = new Environment()
    env.addServer('https://api.com')
    const result = env.getServer()!
    assert.typeOf(result, 'object', 'has the server')
    assert.equal(result.readUri(), 'https://api.com')
  })
})

test.group('Models > Environment > addServer()', () => {
  test('adds a server by URI', ({ assert }) => {
    const env = new Environment()
    env.addServer('https://happy.server')

    assert.equal(env.getServer()!.uri, 'https://happy.server')
  })

  test('adds a server from schema', ({ assert }) => {
    const env = new Environment()
    const srv = new Server()
    srv.uri = 'https://happy.server'
    env.addServer(srv.toJSON())

    assert.equal(env.getServer()!.uri, 'https://happy.server')
  })
})

test.group('Models > Environment > clone()', () => {
  test('makes a copy of the environment', ({ assert }) => {
    const src = Environment.fromName('abc')
    const copy = src.clone()
    assert.equal(copy.info.name, 'abc')
  })

  test('regenerates the key by default', ({ assert }) => {
    const src = Environment.fromName('abc')
    const copy = src.clone()
    assert.notEqual(copy.key, src.key)
    assert.typeOf(copy.key, 'string')
  })

  test('skips key regeneration when configured', ({ assert }) => {
    const src = Environment.fromName('abc')
    const copy = src.clone({ withoutRevalidate: true })
    assert.equal(copy.key, src.key)
  })
})

test.group('Models > Environment > uniqueProperties()', () => {
  test('returns all properties', ({ assert }) => {
    const e1 = Environment.fromName('e1')
    const v1 = e1.addVariable('v1', 'v1')
    const v2 = e1.addVariable('v2', 'v2')
    const result = e1.uniqueProperties()
    assert.deepEqual(result, [v1, v2])
  })

  test('returns only unique properties', ({ assert }) => {
    const e1 = Environment.fromName('e1')
    e1.addVariable('v1', 'v1')
    const v2 = e1.addVariable('v1', 'v2')
    const result = e1.uniqueProperties()
    assert.deepEqual(result, [v2])
  })
})
