import { test, suite } from 'node:test'
import assert from 'node:assert'

import { Iroh, PublicKey, verifyNodeAddr, Query, AuthorId, pathToKey, keyToPath, DocTicket } from '../index.js'

import { tmpdir } from 'node:os'
import { randomBytes } from 'node:crypto'
import { join } from 'node:path'
import { mkdtemp, mkdir, writeFile, readFile } from 'node:fs/promises'

suite('doc', () => {
  test('create doc', async (t) => {
    const node = await Iroh.memory({ enableDocs: true })

    const doc = await node.docs.create()
    const id = doc.id()
    assert.ok(id)

    const author = await node.authors.default()
    const key = Array.from(new Uint8Array([1, 2, 3]))
    const value = Array.from(new Uint8Array([3, 2, 1]))
    const hash = await doc.setBytes(author, key, value)
    assert.ok(hash)

    const entry = await doc.getExact(author, key, false)
    assert.ok(entry)
    assert.equal(entry.hash, hash.toString())
    assert.equal(entry.author, author.toString())
    assert.deepEqual(entry.key, key)
    assert.equal(entry.len, BigInt(key.length))
  })

  test('basic sync', async (t) => {
    const node0 = await Iroh.memory({ enableDocs: true })
    const node1 = await Iroh.memory({ enableDocs: true })

    const doc0 = await node0.docs.create()
    const ticket = await doc0.share('Write', 'RelayAndAddresses')
    const ticketString = ticket.toString()
    const ticketBack = DocTicket.fromString(ticketString)

    // Do not use Promise.withResovlers it is buggy
    let resolve0;
    let reject0;
    const promise0 = new Promise((resolve, reject) => {
      resolve0 = resolve;
      reject0 = reject;
    });
    let resolve1;
    let reject1;
    const promise1 = new Promise((resolve, reject) => {
      resolve1 = resolve;
      reject1 = reject;
    });

    await doc0.subscribe((error, event) => {
      if (error != null) {
        return reject0(error)
      }
      // Wait until the sync is finished
      if (event.contentReady != null) {
        resolve0(event.contentReady.hash)
      }
    })

    const doc1 = await node1.docs.joinAndSubscribe(ticket, (error, event) => {
      if (error != null) {
        return reject1(error)
      }
      // Wait until the sync is finished
      if (event.syncFinished != null) {
        resolve1(event)
      }
    })

    const e = await promise1

    // create content on node1
    const author = await node1.authors.default()
    await doc1.setBytes(
      author,
      Array.from(Buffer.from('hello')),
      Array.from(Buffer.from('world'))
    )


    const hash = await promise0
    const val = await node1.blobs.readToBytes(hash)
    assert.equal(Buffer.from(val).toString('utf8'), 'world')
  })


  test('node addr', (t) => {
    const keyStr = '7db06b57aac9b3640961d281239c8f23487ac7f7265da21607c5612d3527a254'
    const nodeId = PublicKey.fromString(keyStr)

    const ipv4 = '127.0.0.1:3000'
    const ipv6 = '::1:3000'
    const addrs = [ipv4, ipv6]

    const relayUrl = 'https://example.com'

    const nodeAddr = { nodeId: nodeId.toString(), relayUrl, addrs }
    verifyNodeAddr(nodeAddr)
  })

  test('query', async (t) => {
    const query1 = Query.singleLatestPerKey({
      direction: 'Desc',
    })

    assert.equal(query1.offset(), BigInt(0))
    assert.equal(query1.limit(), null)

    const query2 = Query.author(
      AuthorId.fromString('7db06b57aac9b3640961d281239c8f23487ac7f7265da21607c5612d3527a254'),
      {
        direction: 'Asc',
        offset: BigInt(100),
      }
    )
    assert.equal(query2.offset(), BigInt(100))
    assert.equal(query2.limit(), null)

    const query3 = Query.keyExact(
      Array.from(Buffer.from('key')),
      {
        sortBy: 'KeyAuthor',
        offset: BigInt(0),
        limit: BigInt(100),
      }
    )
    assert.equal(query3.offset(), BigInt(0))
    assert.equal(query3.limit(), BigInt(100))

    const query4 = Query.keyPrefix(
      Array.from(Buffer.from('prefix')),
      {
        limit: BigInt(100)
      }
    )
    assert.equal(query3.offset(), BigInt(0))
    assert.equal(query3.limit(), BigInt(100))
  })


  test('import export', async (t) => {
    const dir = await mkdtemp(join(tmpdir(), 'iroh-docs-'));
    const inRoot = join(dir, 'in')
    const outRoot = join(dir, 'out')
    await mkdir(inRoot)
    await mkdir(outRoot)

    const filePath = join(inRoot, 'test')
    const size = 100
    const bytes = randomBytes(size)
    await writeFile(filePath, bytes)

    const node = await Iroh.memory({ enableDocs: true })
    const author = await node.authors.default()
    const doc = await node.docs.create()

    // import entry
    const key = pathToKey(filePath, null, inRoot)
    await doc.importFile(author, key, filePath, true)

    // get entry
    const query = Query.authorKeyExact(author, key)
    const entry = await doc.getOne(query)

    // export entry
    const exportPath = keyToPath(key, null, outRoot)
    await doc.exportFile(entry, exportPath)

    // read file
    const content = await readFile(exportPath)

    assert.deepEqual(content, bytes)
  })
})

Neighbours