/**
 * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

import { mount, shallowMount } from '@vue/test-utils'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
import { h, nextTick } from 'vue'
import NcAvatar from '../../../../src/components/NcAvatar/NcAvatar.vue'

describe('NcAvatar.vue', () => {
	it('aria label is set to include status if status is shown visually', async () => {
		const status = {
			icon: '',
			status: 'online',
			message: 'doing nothing',
		}

		const wrapper = mount(NcAvatar, {
			props: {
				user: 'janedoe',
				displayName: 'J. Doe',
				preloadedUserStatus: status,
			},
		})
		await nextTick()

		expect(wrapper.find('.avatardiv__user-status').exists()).toBe(true)
		expect(wrapper.find('.action-item__menutoggle').attributes('aria-label')).toBe('Avatar of J. Doe, online')
	})

	it('aria label is set to include status even if status is do-not-disturb', async () => {
		const status = {
			icon: '',
			status: 'dnd',
			message: 'Playing DnD',
		}

		const wrapper = mount(NcAvatar, {
			props: {
				user: 'janedoe',
				displayName: 'J. Doe',
				preloadedUserStatus: status,
			},
		})

		await nextTick()

		expect(wrapper.find('.avatardiv__user-status').exists()).toBe(true)
		expect(wrapper.find('.action-item__menutoggle').attributes('aria-label')).toBe('Avatar of J. Doe, do not disturb')
	})

	it('aria label is does not include status if status not shown', async () => {
		const status = {
			icon: '',
			status: 'online',
			message: 'Idle',
		}

		const wrapper = mount(NcAvatar, {
			props: {
				user: 'janedoe',
				displayName: 'J. Doe',
				preloadedUserStatus: status,
				hideStatus: true,
			},
		})

		await nextTick()

		expect(wrapper.find('.avatardiv__user-status').exists()).toBe(false)
		expect(wrapper.find('.action-item__menutoggle').attributes('aria-label')).toBe('Avatar of J. Doe')
	})

	it('should display initials for user id', async () => {
		const wrapper = shallowMount(NcAvatar, {
			props: {
				user: 'Jane Doe',
				isNoUser: true,
			},
		})
		await nextTick()
		expect(wrapper.text()).toBe('JD')
	})

	it('should display initials for display name property over user id', async () => {
		const wrapper = shallowMount(NcAvatar, {
			props: {
				displayName: 'No User',
				user: 'I am a group',
				isNoUser: true,
			},
		})
		await nextTick()
		expect(wrapper.text()).toBe('NU')
	})

	describe('Fallback initials', () => {
		it.each`
			displayName             | initials | case
			${''}                   | ${'?'}   | ${'empty user'}
			${'Jane Doe'}           | ${'JD'}  | ${'display name property'}
			${'Jane Doe !'}         | ${'JD'}  | ${'trailing space before special character'}
			${'Jane (Doe)'}         | ${'JD'}  | ${'special characters in name'}
			${'jane doe'}           | ${'JD'}  | ${'lower case name'}
			${'Jane Some Name Doe'} | ${'JD'}  | ${'middle names'}
			${'Ümit Öçal'}          | ${'ÜÖ'}  | ${'non ascii characters'}
			${'ジェーン ドー'}        | ${'ジド'} | ${'non latin characters'}
		`('should display initials for $case ("$displayName" -> "$initials")', async ({ displayName, initials }) => {
			const wrapper = shallowMount(NcAvatar, {
				props: {
					displayName,
				},
			})
			await nextTick()
			expect(wrapper.text()).toBe(initials)
		})
	})

	describe('Icon and image rendering', () => {
		const ImageConstructor = globalThis.Image

		beforeAll(() => {
			// @ts-expect-error mock Image constructor for tests
			globalThis.Image = class {
				_src: string = ''

				onload() {
					throw new Error('Method not implemented.')
				}

				get src() {
					return this._src
				}

				set src(value: string) {
					this._src = value
					this.onload?.() // Trigger the onload event
				}
			}
		})

		afterAll(() => {
			globalThis.Image = ImageConstructor
		})

		it('should render image with avatar url pointing to a user', async () => {
			const wrapper = mount(NcAvatar, {
				props: {
					displayName: 'Guest',
					user: 'user1',
				},
			})
			await nextTick()

			expect(wrapper.find('img').exists()).toBeTruthy()
			expect(wrapper.find('img').attributes('src')).toMatch(/avatar\/user1\/64\?guestFallback=true$/)
		})

		it('should render image with avatar url pointing to a user', async () => {
			const avatarUrl = 'https://cloud.ltd/index.php/avatar/user2/512'

			const wrapper = mount(NcAvatar, {
				props: {
					displayName: 'Guest',
					url: avatarUrl,
				},
			})
			await nextTick()

			expect(wrapper.find('img').exists()).toBeTruthy()
			expect(wrapper.find('img').attributes('src')).toBe(avatarUrl)
		})

		it('should not render image with avatar url if icon slot is populated', async () => {
			const wrapper = mount(NcAvatar, {
				props: {
					displayName: 'User',
					user: 'userid',
				},
				slots: {
					icon: h('span', { class: 'slot-scoped-icon' }, []),
				},
			})

			expect(wrapper.find('img').exists()).toBeFalsy()
			expect(wrapper.find('span.slot-scoped-icon').exists()).toBeTruthy()
		})

		it('should not render image with avatar url if iconClass is provided', async () => {
			const wrapper = mount(NcAvatar, {
				props: {
					displayName: 'User',
					user: 'userid',
					iconClass: 'custom-icon-class',
				},
			})

			expect(wrapper.find('img').exists()).toBeFalsy()
			expect(wrapper.find('span.avatar-class-icon.custom-icon-class').exists()).toBeTruthy()
		})

		it('should not render image with avatar url if not a user', async () => {
			const wrapper = mount(NcAvatar, {
				props: {
					displayName: 'Guest',
					user: 'guest/abcdef',
					isNoUser: true,
				},
			})

			expect(wrapper.find('img').exists()).toBeFalsy()
		})

		it('should not render image with avatar url if user id is not provided', async () => {
			const wrapper = mount(NcAvatar, {
				props: {
					displayName: 'Empty',
				},
			})

			expect(wrapper.find('img').exists()).toBeFalsy()
		})
	})
})
