package ch.threema.app.stores

import ch.threema.app.eventbus.EventBus
import ch.threema.app.eventbus.events.ProfileEvent
import ch.threema.common.emptyByteArray
import ch.threema.domain.types.Identity
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertNull

class IdentityStoreImplTest {
    @Test
    fun `no identity stored`() {
        val identityProviderMock = mockk<MutableIdentityProvider> {
            every { getIdentity() } returns null
        }
        val preferenceStoreMock = mockk<PreferenceStore>()
        val encryptedPreferenceStoreMock = mockk<EncryptedPreferenceStore>()

        val identityStore = IdentityStoreImpl(
            identityProvider = identityProviderMock,
            preferenceStore = preferenceStoreMock,
            encryptedPreferenceStore = encryptedPreferenceStoreMock,
            globalEventBuses = mockk(),
        )

        assertNull(identityStore.getIdentityString())
        assertNull(identityStore.getPublicKey())
        assertNull(identityStore.getPrivateKey())
        assertNull(identityStore.getServerGroup())
        assertEquals("", identityStore.getPublicNickname())
    }

    @Test
    fun `restoring valid identity`() {
        val identityProviderMock = mockk<MutableIdentityProvider> {
            every { getIdentity() } returns Identity("01234567")
        }
        val preferenceStoreMock = mockk<PreferenceStore> {
            every { getString("server_group") } returns "foobar"
            every { getBytes("public_key") } returns PUBLIC_KEY
            every { getString("nickname") } returns "Nick"
        }
        val encryptedPreferenceStoreMock = mockk<EncryptedPreferenceStore> {
            every { getBytes("private_key") } returns PRIVATE_KEY
        }

        val identityStore = IdentityStoreImpl(
            identityProvider = identityProviderMock,
            preferenceStore = preferenceStoreMock,
            encryptedPreferenceStore = encryptedPreferenceStoreMock,
            globalEventBuses = mockk(),
        )

        assertEquals("01234567", identityStore.getIdentityString())
        assertContentEquals(PUBLIC_KEY, identityStore.getPublicKey())
        assertContentEquals(PRIVATE_KEY, identityStore.getPrivateKey())
        assertEquals("foobar", identityStore.getServerGroup())
        assertEquals("Nick", identityStore.getPublicNickname())
    }

    @Test
    fun `restoring valid identity with missing private key`() {
        val identityProviderMock = mockk<MutableIdentityProvider> {
            every { getIdentity() } returns Identity("01234567")
        }
        val preferenceStoreMock = mockk<PreferenceStore> {
            every { getString("server_group") } returns "foobar"
            every { getBytes("public_key") } returns PUBLIC_KEY
            every { getString("nickname") } returns "Nick"
        }
        val encryptedPreferenceStoreMock = mockk<EncryptedPreferenceStore> {
            every { getBytes("private_key") } returns emptyByteArray()
        }

        val identityStore = IdentityStoreImpl(
            identityProvider = identityProviderMock,
            preferenceStore = preferenceStoreMock,
            encryptedPreferenceStore = encryptedPreferenceStoreMock,
            globalEventBuses = mockk(),
        )

        assertNull(identityStore.getPrivateKey())
    }

    @Test
    fun `restoring invalid identity`() {
        val identityProviderMock = mockk<MutableIdentityProvider> {
            every { getIdentity() } returns Identity("01234567")
        }
        val preferenceStoreMock = mockk<PreferenceStore> {
            every { getString("server_group") } returns "foobar"
            every { getBytes("public_key") } returns PUBLIC_KEY.copyOfRange(0, 30) // wrong length
            every { getString("nickname") } returns "Nick"
        }
        val encryptedPreferenceStoreMock = mockk<EncryptedPreferenceStore> {
            every { getBytes("private_key") } returns PRIVATE_KEY
        }

        assertFailsWith<IllegalArgumentException> {
            IdentityStoreImpl(
                identityProvider = identityProviderMock,
                preferenceStore = preferenceStoreMock,
                encryptedPreferenceStore = encryptedPreferenceStoreMock,
                globalEventBuses = mockk(),
            )
        }
    }

    @Test
    fun `storing identity`() {
        val identityProviderMock = mockk<MutableIdentityProvider>(relaxed = true) {
            every { getIdentity() } returns null
        }
        val preferenceStoreMock = mockk<PreferenceStore>(relaxed = true)
        val encryptedPreferenceStoreMock = mockk<EncryptedPreferenceStore>(relaxed = true)
        val profileEventBusMock = mockk<EventBus<ProfileEvent>>(relaxed = true)
        val identityStore = IdentityStoreImpl(
            identityProvider = identityProviderMock,
            preferenceStore = preferenceStoreMock,
            encryptedPreferenceStore = encryptedPreferenceStoreMock,
            derivePublicKey = { PUBLIC_KEY },
            globalEventBuses = mockk {
                every { profiles } returns profileEventBusMock
            },
        )

        identityStore.storeIdentity(
            identity = "01234567",
            serverGroup = "foobar",
            privateKey = PRIVATE_KEY,
        )

        verify { identityProviderMock.setIdentity(Identity("01234567")) }
        verify { preferenceStoreMock.save("server_group", "foobar") }
        verify { preferenceStoreMock.save("public_key", PUBLIC_KEY) }
        verify { encryptedPreferenceStoreMock.save("private_key", PRIVATE_KEY) }
        verify { preferenceStoreMock.save("nickname", "01234567") }
        assertEquals("01234567", identityStore.getIdentityString())
        assertContentEquals(PUBLIC_KEY, identityStore.getPublicKey())
        assertContentEquals(PRIVATE_KEY, identityStore.getPrivateKey())
        assertEquals("foobar", identityStore.getServerGroup())
        assertEquals("01234567", identityStore.getPublicNickname())
        verify(exactly = 1) { profileEventBusMock.emit(ProfileEvent.NicknameUpdated("01234567")) }
    }

    @Test
    fun `setting public nickname`() {
        val identityProviderMock = mockk<MutableIdentityProvider> {
            every { getIdentity() } returns null
        }
        val preferenceStoreMock = mockk<PreferenceStore>(relaxed = true)
        val encryptedPreferenceStoreMock = mockk<EncryptedPreferenceStore>()
        val profileEventBusMock = mockk<EventBus<ProfileEvent>>(relaxed = true)
        val identityStore = IdentityStoreImpl(
            identityProvider = identityProviderMock,
            preferenceStore = preferenceStoreMock,
            encryptedPreferenceStore = encryptedPreferenceStoreMock,
            globalEventBuses = mockk {
                every { profiles } returns profileEventBusMock
            },
        )

        identityStore.setPublicNickname("Nicolas")

        assertEquals("Nicolas", identityStore.getPublicNickname())
        verify(exactly = 1) { profileEventBusMock.emit(ProfileEvent.NicknameUpdated("Nicolas")) }
        verify { preferenceStoreMock.save("nickname", "Nicolas") }
    }

    @Test
    fun `clearing identity`() {
        val identityProviderMock = mockk<MutableIdentityProvider>(relaxed = true) {
            every { getIdentity() } returns Identity("01234567")
        }
        val preferenceStoreMock = mockk<PreferenceStore>(relaxed = true) {
            every { getString("server_group") } returns "foobar"
            every { getBytes("public_key") } returns PUBLIC_KEY
            every { getString("nickname") } returns "Nick"
        }
        val encryptedPreferenceStoreMock = mockk<EncryptedPreferenceStore>(relaxed = true) {
            every { getBytes("private_key") } returns PRIVATE_KEY
        }
        val identityStore = IdentityStoreImpl(
            identityProvider = identityProviderMock,
            preferenceStore = preferenceStoreMock,
            encryptedPreferenceStore = encryptedPreferenceStoreMock,
            globalEventBuses = mockk(),
        )

        identityStore.clear()

        verify { identityProviderMock.setIdentity(null) }
        verify { preferenceStoreMock.remove(setOf("public_key", "server_group")) }
        verify { encryptedPreferenceStoreMock.remove("private_key") }
        assertNull(identityStore.getIdentityString())
        assertNull(identityStore.getPublicKey())
        assertNull(identityStore.getPrivateKey())
        assertNull(identityStore.getServerGroup())
        assertEquals("", identityStore.getPublicNickname())
    }

    companion object {
        val PUBLIC_KEY = ByteArray(32) { it.toByte() }
        val PRIVATE_KEY = ByteArray(32) { (it + 1).toByte() }
    }
}
