import {
  setupApphierarchy,
  findCollectionDefaultIndex,
  basicAppHierarchyCreator_WithFields_AndIndexes,
} from "./specHelpers"
import { joinKey } from "../src/common"
import { some } from "lodash"
import { _deleteIndex } from "../src/indexApi/delete"
import { permission } from "../src/authApi/permissions"
import { getExactNodeForKey } from "../src/templateApi/hierarchy"

describe("buildIndex > Global index", () => {
  it("should index a record when record node is not decendant", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const customer = recordApi.getNew(
      appHierarchy.customerRecord.collectionNodeKey(),
      "customer"
    )

    customer.surname = "thedog"

    await recordApi.save(customer)

    const outstandingInvoice = recordApi.getNewChild(
      customer.key,
      "invoices",
      "invoice"
    )

    outstandingInvoice.totalIncVat = 100
    outstandingInvoice.paidAmount = 50

    await recordApi.save(outstandingInvoice)

    const paidInvoice = recordApi.getNewChild(
      customer.key,
      "invoices",
      "invoice"
    )

    paidInvoice.totalIncVat = 200
    paidInvoice.paidAmount = 200

    await recordApi.save(paidInvoice)

    const indexKey = appHierarchy.outstandingInvoicesIndex.nodeKey()
    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(indexKey)
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(1)
    expect(indexItems[0].key).toBe(outstandingInvoice.key)
  })

  it("should index records from 2 seperate tree branches", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const customer = recordApi.getNew(
      appHierarchy.customerRecord.collectionNodeKey(),
      "customer"
    )

    customer.surname = "thedog"

    await recordApi.save(customer)

    const invoice = recordApi.getNewChild(customer.key, "invoices", "invoice")

    invoice.totalIncVat = 100
    invoice.paidAmount = 50

    await recordApi.save(invoice)

    const partner = recordApi.getNew(
      appHierarchy.partnerRecord.collectionNodeKey(),
      "partner"
    )

    partner.surname = "thedog"

    await recordApi.save(partner)

    const partnerInvoice = recordApi.getNewChild(
      partner.key,
      "invoices",
      "invoice"
    )

    partnerInvoice.totalIncVat = 100
    partnerInvoice.paidAmount = 50

    await recordApi.save(partnerInvoice)

    const indexKey = appHierarchy.outstandingInvoicesIndex.nodeKey()
    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(indexKey)
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(2)
    expect(indexItems[0].key).toBe(invoice.key)
    expect(indexItems[1].key).toBe(partnerInvoice.key)
  })
})

describe("buildIndex > TopLevelCollection", () => {
  it("should index a record when it is a nested decendant of the collection node", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const customer = recordApi.getNew(
      appHierarchy.customerRecord.collectionNodeKey(),
      "customer"
    )

    customer.surname = "thedog"

    await recordApi.save(customer)

    const invoice = recordApi.getNewChild(customer.key, "invoices", "invoice")

    invoice.totalIncVat = 100
    invoice.paidAmount = 50

    await recordApi.save(invoice)

    const indexKey = appHierarchy.customerInvoicesIndex.nodeKey()
    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(indexKey)
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(1)
    expect(indexItems[0].key).toBe(invoice.key)
  })

  it("should not index a record when it is not decendant", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const partner = recordApi.getNew(
      appHierarchy.partnerRecord.collectionNodeKey(),
      "partner"
    )

    partner.surname = "thedog"

    await recordApi.save(partner)

    const invoice = recordApi.getNewChild(partner.key, "invoices", "invoice")

    invoice.totalIncVat = 100
    invoice.paidAmount = 50

    await recordApi.save(invoice)

    const indexKey = appHierarchy.customerInvoicesIndex.nodeKey()
    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(indexKey)
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(0)
  })
})

describe("buildIndex > nested collection", () => {
  it("should build a single record into index", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const customer = recordApi.getNew(
      appHierarchy.customerRecord.collectionNodeKey(),
      "customer"
    )

    customer.surname = "thedog"

    await recordApi.save(customer)

    const invoice = recordApi.getNewChild(customer.key, "invoices", "invoice")

    invoice.totalIncVat = 100
    invoice.paidAmount = 50

    await recordApi.save(invoice)

    const indexKey = joinKey(customer.key, "invoice_index")
    await _deleteIndex(app, indexKey, false)

    const indexNode = getExactNodeForKey(appHierarchy.root)(indexKey)
    await indexApi.buildIndex(indexNode.nodeKey())
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(1)
    expect(indexItems[0].key).toBe(invoice.key)
  })

  it("should build multiple records, from different parents into index", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const customer = recordApi.getNew(
      appHierarchy.customerRecord.collectionNodeKey(),
      "customer"
    )

    customer.surname = "thedog"

    await recordApi.save(customer)

    const invoice = recordApi.getNewChild(customer.key, "invoices", "invoice")

    invoice.totalIncVat = 100
    invoice.paidAmount = 50

    await recordApi.save(invoice)

    const customer2 = recordApi.getNew(
      appHierarchy.customerRecord.collectionNodeKey(),
      "customer"
    )

    customer2.surname = "thedog"

    await recordApi.save(customer2)

    const invoice2 = recordApi.getNewChild(customer2.key, "invoices", "invoice")

    invoice2.totalIncVat = 100
    invoice2.paidAmount = 50

    await recordApi.save(invoice2)

    const indexKey = joinKey(customer.key, "invoice_index")
    await _deleteIndex(app, indexKey, false)

    const indexNode = getExactNodeForKey(appHierarchy.root)(indexKey)
    await indexApi.buildIndex(indexNode.nodeKey())
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(1)
    expect(some(indexItems, i => i.key === invoice.key)).toBeTruthy()

    const indexItems2 = await indexApi.listItems(
      joinKey(customer2.key, "invoice_index")
    )

    expect(indexItems2.length).toBe(1)
    expect(some(indexItems2, i => i.key === invoice2.key)).toBeTruthy()
  })
})

describe("buildIndex > sharded index", () => {
  it("should index a record into a sharded index", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const customer = recordApi.getNew(
      appHierarchy.customerRecord.collectionNodeKey(),
      "customer"
    )

    customer.surname = "thedog"

    await recordApi.save(customer)

    const indexKey = appHierarchy.customersBySurnameIndex.nodeKey()

    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(indexKey)
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(1)
    expect(indexItems[0].key).toBe(customer.key)
  })

  it("should index multiple record into a sharded index", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const customer1 = recordApi.getNew(
      appHierarchy.customerRecord.collectionNodeKey(),
      "customer"
    )
    customer1.surname = "thedog"
    await recordApi.save(customer1)

    const customer2 = recordApi.getNew(
      appHierarchy.customerRecord.collectionNodeKey(),
      "customer"
    )
    customer2.surname = "Zeecat"
    await recordApi.save(customer2)

    const indexKey = appHierarchy.customersBySurnameIndex.nodeKey()

    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(indexKey)
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(2)
    expect(some(indexItems, i => i.key === customer1.key)).toBeTruthy()
    expect(some(indexItems, i => i.key === customer2.key)).toBeTruthy()
  })

  it("should index multiple records into a sharded and nested index", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const customer = recordApi.getNew(
      appHierarchy.customerRecord.collectionNodeKey(),
      "customer"
    )
    customer.surname = "thedog"
    await recordApi.save(customer)

    const invoiceCollectionKey = joinKey(customer.key, "invoices")

    const invoice1 = recordApi.getNew(invoiceCollectionKey, "invoice")
    invoice1.totalIncVat = 10
    invoice1.paidAmount = 10
    await recordApi.save(invoice1)

    const invoice2 = recordApi.getNew(invoiceCollectionKey, "invoice")
    invoice2.totalIncVat = 10
    invoice2.paidAmount = 0
    await recordApi.save(invoice2)

    const indexKey = joinKey(
      customer.key,
      appHierarchy.invoicesByOutstandingIndex.name
    )

    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(appHierarchy.invoicesByOutstandingIndex.nodeKey())
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(2)
    expect(some(indexItems, i => i.key === invoice1.key)).toBeTruthy()
    expect(some(indexItems, i => i.key === invoice2.key)).toBeTruthy()

    const outstandingRange = { totalIncVat: 1, paidAmount: 0 }
    const outstandingItems = await indexApi.listItems(indexKey, {
      rangeStartParams: outstandingRange,
      rangeEndParams: outstandingRange,
    })
    expect(outstandingItems.length).toBe(1)
  })

  it("should build reverse reference index", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const referencedCustomer = recordApi.getNew("/customers", "customer")
    referencedCustomer.surname = "Zecat"
    await recordApi.save(referencedCustomer)

    const referencingCustomer = recordApi.getNew("/customers", "customer")
    referencingCustomer.surname = "Ledog"
    referencingCustomer.referredBy = {
      key: referencedCustomer.key,
      value: referencedCustomer.surname,
    }
    referencingCustomer.isalive = true

    await recordApi.save(referencingCustomer)

    const indexKey = joinKey(referencedCustomer.key, "referredToCustomers")

    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(
      appHierarchy.referredToCustomersReverseIndex.nodeKey()
    )

    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(1)
    expect(
      some(indexItems, i => i.key === referencingCustomer.key)
    ).toBeTruthy()
  })
})

describe("buildIndex > reverse reference index", () => {
  it("should build a single record into index", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const partner1 = recordApi.getNew("/partners", "partner")
    partner1.businessName = "ACME inc"
    await recordApi.save(partner1)

    const customer = recordApi.getNew("/customers", "customer")
    customer.surname = "Ledog"
    customer.partner = {
      key: partner1.key,
      value: partner1.businessName,
    }
    customer.isalive = true

    await recordApi.save(customer)

    const indexKey = joinKey(partner1.key, "partnerCustomers")

    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(
      appHierarchy.partnerCustomersReverseIndex.nodeKey()
    )
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(1)
    expect(some(indexItems, i => i.key === customer.key)).toBeTruthy()
  })

  it("should build multiple records into an index, when referencing same record", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const partner1 = recordApi.getNew("/partners", "partner")
    partner1.businessName = "ACME inc"
    await recordApi.save(partner1)

    const customer1 = recordApi.getNew("/customers", "customer")
    customer1.surname = "Ledog"
    customer1.partner = {
      key: partner1.key,
      value: partner1.businessName,
    }
    customer1.isalive = true

    await recordApi.save(customer1)

    const customer2 = recordApi.getNew("/customers", "customer")
    customer2.surname = "Zeecat"
    customer2.partner = {
      key: partner1.key,
      value: partner1.businessName,
    }
    customer2.isalive = true

    await recordApi.save(customer2)

    const indexKey = joinKey(partner1.key, "partnerCustomers")

    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(
      appHierarchy.partnerCustomersReverseIndex.nodeKey()
    )
    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(2)
    expect(some(indexItems, i => i.key === customer1.key)).toBeTruthy()
    expect(some(indexItems, i => i.key === customer2.key)).toBeTruthy()
  })

  it("should build multiple records into seperate indexes, when referencing different records", async () => {
    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const partner1 = recordApi.getNew("/partners", "partner")
    partner1.businessName = "ACME inc"
    await recordApi.save(partner1)

    const partner2 = recordApi.getNew("/partners", "partner")
    partner2.businessName = "ACME inc"
    await recordApi.save(partner2)

    const customer1 = recordApi.getNew("/customers", "customer")
    customer1.surname = "Ledog"
    customer1.partner = {
      key: partner1.key,
      value: partner1.businessName,
    }
    customer1.isalive = true

    await recordApi.save(customer1)

    const customer2 = recordApi.getNew("/customers", "customer")
    customer2.surname = "Zeecat"
    customer2.partner = {
      key: partner2.key,
      value: partner1.businessName,
    }
    customer2.isalive = true

    await recordApi.save(customer2)

    const indexKey1 = joinKey(partner1.key, "partnerCustomers")
    const indexKey2 = joinKey(partner2.key, "partnerCustomers")

    await _deleteIndex(app, indexKey1, false)

    await _deleteIndex(app, indexKey2, false)

    await indexApi.buildIndex(
      appHierarchy.partnerCustomersReverseIndex.nodeKey()
    )

    const indexItems1 = await indexApi.listItems(indexKey1)

    expect(indexItems1.length).toBe(1)
    expect(some(indexItems1, i => i.key === customer1.key)).toBeTruthy()

    const indexItems2 = await indexApi.listItems(indexKey2)

    expect(indexItems2.length).toBe(1)
    expect(some(indexItems2, i => i.key === customer2.key)).toBeTruthy()
  })

  it.skip("should build record into index, when referencing and referenced records are in multiple nested collections", async () => {
    // this currently fails because it is currntly assumed that the reference index should be either
    // - Top level index
    // - An ancestor index
    // this test sets "customers/<id>/invoices/<id>/charges/<id>""to point to..
    //  "/partners/<id>/invoices/default"
    //
    // To work as intended, we would need to somehow find the index by:
    //  - customer.partner.key + /invoices/default
    // bearing in mind that the customer is an ancestor.

    const { recordApi, indexApi, appHierarchy, app } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )

    const partner = recordApi.getNew("/partners", "partner")
    partner.businessName = "ACME inc"
    await recordApi.save(partner)

    const partnerInvoice = recordApi.getNew(
      joinKey(partner.key, "invoices"),
      "invoice"
    )
    await recordApi.save(partnerInvoice)

    const customer = recordApi.getNew("/customers", "customer")
    customer.surname = "Ledog"
    await recordApi.save(customer)

    const customerInvoice = recordApi.getNew(
      joinKey(customer.key, "invoices"),
      "invoice"
    )
    await recordApi.save(customerInvoice)

    const charge = recordApi.getNew(
      joinKey(customerInvoice.key, "charges"),
      "charge"
    )
    charge.partnerInvoice = {
      key: partnerInvoice.key,
      createdDate: "something",
    }
    await recordApi.save(charge)

    const indexKey = joinKey(partnerInvoice.key, "partnerCharges")

    await _deleteIndex(app, indexKey, false)

    await indexApi.buildIndex(appHierarchy.partnerChargesReverseIndex.nodeKey())

    const indexItems = await indexApi.listItems(indexKey)

    expect(indexItems.length).toBe(1)
    expect(some(indexItems, i => i.key === charge.key)).toBeTruthy()
  })

  it("should throw error when user user does not have permission", async () => {
    const { indexApi, app, appHierarchy } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )
    app.removePermission(permission.manageIndex.get())
    expect(
      indexApi.buildIndex(appHierarchy.partnerCustomersReverseIndex.nodeKey())
    ).rejects.toThrow(/Unauthorized/)
  })

  it("should not depend on having any other permissions", async () => {
    const { app, indexApi, appHierarchy } = await setupApphierarchy(
      basicAppHierarchyCreator_WithFields_AndIndexes
    )
    app.withOnlyThisPermission(permission.manageIndex.get())
    await indexApi.buildIndex(
      appHierarchy.partnerCustomersReverseIndex.nodeKey()
    )
  })
})