// *****************************************************************************
// Copyright 2013-2024 Aerospike, Inc.
//
// 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.
// *****************************************************************************

'use strict'

/* eslint-env mocha */
/* global expect */


import { Job as J, IndexJob as IJ, Client as Cli, AerospikeError } from '../lib/aerospike.js';

import { expect } from 'chai'; 
import * as helper from './test_helper.ts';
import * as Aerospike from '../lib/aerospike.js'; 

const Job: typeof J = Aerospike.Job
const IndexJob: typeof IJ = Aerospike.IndexJob

const Context = Aerospike.cdt.Context

context('secondary indexes', function () {
  const client: Cli = helper.client

  // generate unique index name for each test
  const testIndex: any = { name: null, bin: null, counter: 0 }
  beforeEach(() => {
    testIndex.counter++
    testIndex.name = 'idx-' + testIndex.counter + '-' + Math.floor(Math.random() * 10000000)
    testIndex.bin = 'bin-' + testIndex.counter + '-' + Math.floor(Math.random() * 10000000)
  })

  function verifyIndexExists (namespace: string, indexName: string) {
    // TODO: this info command is deprecated in server 8.1
    const sindex = 'sindex/' + namespace + '/' + indexName
    const checkStatus = function () {
      return client.infoAll(sindex)
        .then(() => true)
        .catch((error: any) => {
          if (error.code !== Aerospike.status.ERR_INDEX_NOT_FOUND) {
            return Promise.reject(error)
          }
          return false
        })
    }
    return (Job as any).pollUntilDone(checkStatus, 10)
      .then(() => helper.index.remove(indexName))
  }

  describe('Client#indexCreate()', function () {
    it('returns an IndexJob instance', function () {
      const options = {
        ns: helper.namespace,
        set: helper.set,
        bin: testIndex.bin,
        index: testIndex.name,
        datatype: Aerospike.indexDataType.NUMERIC
      }

      return client.createIndex(options)
        .then((job: IJ) => expect(job).to.be.instanceof(IndexJob))
        .then(() => verifyIndexExists(helper.namespace, testIndex.name))
    })

    it('should create a complex index on list', function () {
      const options = {
        ns: helper.namespace,
        set: helper.set,
        bin: testIndex.bin,
        index: testIndex.name,
        type: Aerospike.indexType.LIST,
        datatype: Aerospike.indexDataType.NUMERIC
      }

      return client.createIndex(options)
        .then(() => verifyIndexExists(helper.namespace, testIndex.name))
    })

    it('should create an index with CDT Context', function () {
      const options = {
        ns: helper.namespace,
        set: helper.set,
        bin: testIndex.bin,
        index: testIndex.name,
        type: Aerospike.indexType.LIST,
        datatype: Aerospike.indexDataType.NUMERIC,
        context: new Context().addListIndex(0)
      }

      return client.createIndex(options)
        .then(() => verifyIndexExists(helper.namespace, testIndex.name))
    })

    it('should not create an index with CDT Context \'addListIndexCreate\'', function () {
      const options = {
        ns: helper.namespace,
        set: helper.set,
        bin: testIndex.bin,
        index: testIndex.name,
        type: Aerospike.indexType.LIST,
        datatype: Aerospike.indexDataType.NUMERIC,
        context: new Context().addListIndexCreate(0, 0, false)
      }

      return client.createIndex(options)
        .then(() => expect(1).to.equal(2))
        .catch(() => { expect('pass').to.equal('pass') })
    })

    it('should create an integer index with info policy', function () {
      const options = {
        ns: helper.namespace,
        set: helper.set,
        bin: testIndex.bin,
        index: testIndex.name,
        datatype: Aerospike.indexDataType.NUMERIC
      }
      const policy = new Aerospike.InfoPolicy({
        timeout: 100
      })

      return client.createIndex(options, policy)
        .then(() => verifyIndexExists(helper.namespace, testIndex.name))
    })

    it('re-creating an index with identical options returns an error (success with new server, verify the existence)', function () {
      const options = {
        ns: helper.namespace,
        set: helper.set,
        bin: testIndex.bin,
        index: testIndex.name,
        datatype: Aerospike.indexDataType.NUMERIC
      }

      return client.createIndex(options)
        .then((job: IJ) => job.wait(10))
        .then(() => client.createIndex(options)
          .catch((error: any) => {
            if (error.code === Aerospike.status.ERR_INDEX_FOUND ||
              error.code === Aerospike.status.AEROSPIKE_OK) {
              // All good!
              verifyIndexExists(helper.namespace, testIndex.name)
            } else {
              return Promise.reject(error)
            }
          }))
    })

    context('set indexes', function() {
      helper.skipUnlessVersion('>= 8.1.2', this)
      it('should support creating set indexes', function () {
        const options = {
          ns: helper.namespace,
          set: helper.set,
          index: testIndex.name,
          bin: "unused",
          type: Aerospike.indexType.SET,
          datatype: Aerospike.indexDataType.DEFAULT
        }

        return client.createIndex(options)
          .then((job: IJ) => job.wait(10))
          .then(() => client.createIndex(options)
            .catch((error: any) => {
              if (error.code === Aerospike.status.ERR_INDEX_FOUND ||
                error.code === Aerospike.status.AEROSPIKE_OK) {
                // All good!
                verifyIndexExists(helper.namespace, testIndex.name)
              } else {
                return Promise.reject(error)
              }
        }))
      })
    })
  })

  describe('Client#createIntegerIndex()', function () {
    it('should create an integer index', function () {
      const options = {
        ns: helper.namespace,
        set: helper.set,
        bin: testIndex.bin,
        index: testIndex.name
      }

      return client.createIntegerIndex(options)
        .then(() => verifyIndexExists(helper.namespace, testIndex.name))
    })
  })

  describe('Client#createStringIndex()', function () {
    it('should create an string index', function () {
      const args = {
        ns: helper.namespace,
        set: helper.set,
        bin: testIndex.bin,
        index: testIndex.name
      }

      return client.createStringIndex(args)
        .then(() => verifyIndexExists(helper.namespace, testIndex.name))
    })
  })

  describe('Client#createGeo2DSphereIndex()', function () {
    it('should create a geospatial index', function () {
      const args = {
        ns: helper.namespace,
        set: helper.set,
        bin: testIndex.bin,
        index: testIndex.name
      }

      return client.createGeo2DSphereIndex(args)
        .then(() => verifyIndexExists(helper.namespace, testIndex.name))
    })
  })

  describe('Client#createBlobIndex()', function () {
    helper.skipUnlessVersion('>= 7.0.0', this)
    it('should create a blob index', function () {
      const args = {
        ns: helper.namespace,
        set: helper.set,
        bin: testIndex.bin,
        index: testIndex.name
      }

      return client.createBlobIndex(args)
        .then(() => verifyIndexExists(helper.namespace, testIndex.name))
    })
  })

  describe('Client#indexRemove()', async function () {
    beforeEach(async () => {
      await helper.index.create(testIndex.name, helper.set, testIndex.bin,
        Aerospike.indexDataType.STRING, Aerospike.indexType.DEFAULT)
    })

    it('should drop an index', async function () {
      // Wait for index creation to complete
      this.timeout(10000)
      await new Promise(resolve => setTimeout(resolve, 5000))

      // Do query on the secondary index to ensure proper creation.
      let query = client.query(helper.namespace, helper.set)
      query.where(Aerospike.filter.equal(testIndex.bin, 'value'))
      await query.results()

      await client.indexRemove(helper.namespace, testIndex.name)

      // Do query on the secondary index to ensure proper deletion
      query = client.query(helper.namespace, helper.set)
      query.where(Aerospike.filter.equal(testIndex.bin, 'value'))
      try {
        await query.results()
        // Fail test if this code is reached
        expect('fail').to.equal('now')
      } catch (error: any) {
        expect(error.code).to.equal(201)
        expect('pass').to.equal('pass')
      }
    })

    it('should return a Promise if called without callback function', async function () {
      return await client.indexRemove(helper.namespace, testIndex.name)
    })
  })
})
