import { DataSource } from 'typeorm'
import { DataSources } from '@sphereon/ssi-sdk.agent-config'
import { DataStoreStatusListEntities, StatusListEntryEntity } from '../index'
import { DataStoreStatusListMigrations } from '../migrations'
import { OAuthStatusListEntity, StatusList2021Entity } from '../entities/statusList/StatusListEntities'
import { IIssuer, StatusListCredentialIdMode, StatusListDriverType } from '@sphereon/ssi-types'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'

describe('Status list entities tests', () => {
  let dbConnection: DataSource

  beforeEach(async () => {
    DataSources.singleInstance().defaultDbType = 'sqlite'
    dbConnection = await new DataSource({
      type: 'sqlite',
      database: ':memory:',
      migrationsRun: false,
      migrations: DataStoreStatusListMigrations,
      synchronize: false,
      entities: [...DataStoreStatusListEntities],
    }).initialize()
    await dbConnection.runMigrations()
    expect(await dbConnection.showMigrations()).toBeFalsy()
  })

  afterEach(async () => {
    await dbConnection.destroy()
  })

  it('should save status list to database', async () => {
    const statusList = new StatusList2021Entity()
    statusList.id = 'test-list-1'
    statusList.correlationId = 'correlation-1'
    statusList.driverType = StatusListDriverType.AGENT_TYPEORM
    statusList.length = 100000
    statusList.credentialIdMode = StatusListCredentialIdMode.ISSUANCE
    statusList.proofFormat = 'jwt'
    statusList.statusPurpose = 'revocation'
    statusList.indexingDirection = 'rightToLeft'
    statusList.issuer = 'did:example:123'

    const fromDb = await dbConnection.getRepository(StatusList2021Entity).save(statusList)
    expect(fromDb).toBeDefined()
    expect(fromDb.id).toEqual(statusList.id)
    expect(fromDb.correlationId).toEqual(statusList.correlationId)
    expect(fromDb.length).toEqual(statusList.length)
    expect(fromDb.credentialIdMode).toEqual(statusList.credentialIdMode)
    expect(fromDb.statusPurpose).toEqual(statusList.statusPurpose)
    expect(fromDb.indexingDirection).toEqual(statusList.indexingDirection)
    expect(fromDb.issuer).toEqual(statusList.issuer)
  })

  it('should save status list entry to database', async () => {
    const statusList = new StatusList2021Entity()
    statusList.id = 'test-list-1'
    statusList.correlationId = 'correlation-1'
    statusList.driverType = StatusListDriverType.AGENT_TYPEORM
    statusList.length = 100000
    statusList.credentialIdMode = StatusListCredentialIdMode.ISSUANCE
    statusList.proofFormat = 'jwt'
    statusList.statusPurpose = 'revocation'
    statusList.indexingDirection = 'rightToLeft'
    statusList.issuer = 'did:example:123'

    await dbConnection.getRepository(StatusList2021Entity).save(statusList)

    const entry = new StatusListEntryEntity()
    entry.statusList = statusList
    entry.statusListIndex = 1
    entry.credentialId = 'credential-1'
    entry.credentialHash = 'hash-1'
    entry.statusList.correlationId = 'correlation-1'
    entry.value = '1'

    const fromDb = await dbConnection.getRepository(StatusListEntryEntity).save(entry)
    expect(fromDb).toBeDefined()
    expect(fromDb.statusListIndex).toEqual(entry.statusListIndex)
    expect(fromDb.credentialId).toEqual(entry.credentialId)
    expect(fromDb.credentialHash).toEqual(entry.credentialHash)
    expect(fromDb.statusList.correlationId).toEqual(entry.statusList.correlationId)
    expect(fromDb.value).toEqual(entry.value)
  })

  it('should handle complex issuer object', async () => {
    const statusList = new StatusList2021Entity()
    statusList.id = 'test-list-1'
    statusList.correlationId = 'correlation-1'
    statusList.driverType = StatusListDriverType.AGENT_TYPEORM
    statusList.length = 100000
    statusList.credentialIdMode = StatusListCredentialIdMode.ISSUANCE
    statusList.proofFormat = 'jwt'
    statusList.statusPurpose = 'revocation'
    statusList.indexingDirection = 'rightToLeft'
    statusList.issuer = { id: 'did:example:123', name: 'Test Issuer' }

    const fromDb = await dbConnection.getRepository(StatusList2021Entity).save(statusList)
    expect(fromDb).toBeDefined()
    expect(fromDb.issuer).toEqual(statusList.issuer)
    expect(typeof fromDb.issuer).toEqual('object')
    expect((fromDb.issuer as IIssuer).id).toEqual('did:example:123')
    expect((fromDb.issuer as IIssuer).name).toEqual('Test Issuer')
  })

  it('should save OAuth status list to database', async () => {
    const statusList = new OAuthStatusListEntity()
    statusList.id = 'oauth-list-1'
    statusList.correlationId = 'correlation-oauth-1'
    statusList.driverType = StatusListDriverType.AGENT_TYPEORM
    statusList.length = 100000
    statusList.credentialIdMode = StatusListCredentialIdMode.ISSUANCE
    statusList.proofFormat = 'jwt'
    statusList.bitsPerStatus = 1
    statusList.expiresAt = new Date('2025-01-01T00:00:00Z')
    statusList.issuer = 'did:example:123'

    const fromDb = await dbConnection.getRepository(OAuthStatusListEntity).save(statusList)
    expect(fromDb).toBeDefined()
    expect(fromDb.id).toEqual(statusList.id)
    expect(fromDb.correlationId).toEqual(statusList.correlationId)
    expect(fromDb.length).toEqual(statusList.length)
    expect(fromDb.credentialIdMode).toEqual(statusList.credentialIdMode)
    expect(fromDb.bitsPerStatus).toEqual(statusList.bitsPerStatus)
    expect(fromDb.expiresAt).toEqual(statusList.expiresAt)
    expect(fromDb.issuer).toEqual(statusList.issuer)
  })

  it('should handle both status list types having entries', async () => {
    const statusList2021 = new StatusList2021Entity()
    statusList2021.id = 'test-list-1'
    statusList2021.correlationId = 'correlation-1'
    statusList2021.driverType = StatusListDriverType.AGENT_TYPEORM
    statusList2021.length = 100000
    statusList2021.credentialIdMode = StatusListCredentialIdMode.ISSUANCE
    statusList2021.proofFormat = 'jwt'
    statusList2021.statusPurpose = 'revocation'
    statusList2021.indexingDirection = 'rightToLeft'
    statusList2021.issuer = 'did:example:123'
    await dbConnection.getRepository(StatusList2021Entity).save(statusList2021)

    const oauthStatusList = new OAuthStatusListEntity()
    oauthStatusList.id = 'oauth-list-1'
    oauthStatusList.correlationId = 'correlation-oauth-1'
    oauthStatusList.driverType = StatusListDriverType.AGENT_TYPEORM
    oauthStatusList.length = 100000
    oauthStatusList.credentialIdMode = StatusListCredentialIdMode.ISSUANCE
    oauthStatusList.proofFormat = 'jwt'
    oauthStatusList.bitsPerStatus = 1
    oauthStatusList.issuer = 'did:example:456'
    await dbConnection.getRepository(OAuthStatusListEntity).save(oauthStatusList)

    const entry2021 = new StatusListEntryEntity()
    entry2021.statusList = statusList2021
    entry2021.statusListIndex = 1
    entry2021.credentialId = 'credential-1'
    entry2021.credentialHash = 'hash-1'
    entry2021.value = '1'
    await dbConnection.getRepository(StatusListEntryEntity).save(entry2021)

    const entryOAuth = new StatusListEntryEntity()
    entryOAuth.statusList = oauthStatusList
    entryOAuth.statusListIndex = 1
    entryOAuth.credentialId = 'credential-2'
    entryOAuth.credentialHash = 'hash-2'
    entryOAuth.value = '1'
    await dbConnection.getRepository(StatusListEntryEntity).save(entryOAuth)

    const found2021Entry = await dbConnection.getRepository(StatusListEntryEntity).findOne({
      where: { statusList: { id: statusList2021.id }, statusListIndex: 1 },
      relations: { statusList: true },
    })
    const foundOAuthEntry = await dbConnection.getRepository(StatusListEntryEntity).findOne({
      where: { statusList: { id: oauthStatusList.id }, statusListIndex: 1 },
      relations: { statusList: true },
    })
    expect(found2021Entry).toBeTruthy()
    expect(found2021Entry?.credentialId).toEqual('credential-1')
    expect(foundOAuthEntry).toBeTruthy()
    expect(foundOAuthEntry?.credentialId).toEqual('credential-2')
  })

  it('should cascade delete entries when status list is deleted', async () => {
    const statusList = new StatusList2021Entity()
    statusList.id = 'test-list-1'
    statusList.correlationId = 'correlation-1'
    statusList.driverType = StatusListDriverType.AGENT_TYPEORM
    statusList.length = 100000
    statusList.credentialIdMode = StatusListCredentialIdMode.ISSUANCE
    statusList.proofFormat = 'jwt'
    statusList.statusPurpose = 'revocation'
    statusList.indexingDirection = 'rightToLeft'
    statusList.issuer = 'did:example:123'

    const savedStatusList = await dbConnection.getRepository(StatusList2021Entity).save(statusList)

    const entry = new StatusListEntryEntity()
    entry.statusList = statusList
    entry.statusListIndex = 1
    entry.credentialId = 'credential-1'
    entry.credentialHash = 'hash-1'
    entry.statusList.correlationId = 'correlation-1'
    entry.value = '1'

    await dbConnection.getRepository(StatusListEntryEntity).save(entry)

    // First delete entry, otherwise constraint fails
    await dbConnection.getRepository(StatusListEntryEntity).delete({ statusListId: savedStatusList.id })
    await dbConnection.getRepository(StatusList2021Entity).remove(savedStatusList)

    const foundEntry = await dbConnection.getRepository(StatusListEntryEntity).findOne({
      where: {
        statusList: statusList.id,
        statusListIndex: entry.statusListIndex,
      },
    })
    expect(foundEntry).toBeNull()
  })
})
