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

import { expect } from '@playwright/test'
import type { Page } from '@playwright/test'
import { test } from '../support/fixtures/random-user'
import {
	addTextElement,
	createWhiteboard,
	getCanvasForInteraction,
	openFilesApp,
	waitForCanvas,
} from '../support/utils'

async function createPublicShareLink(
	page: Page,
	baseName: string,
	{ allowWhiteboardFallback = true }: { allowWhiteboardFallback?: boolean } = {},
): Promise<string> {
	const requestToken = await page.evaluate(() => (window as any).OC?.requestToken ?? null)

	const candidates = (() => {
		if (!allowWhiteboardFallback) {
			return [baseName]
		}
		const lower = baseName.toLowerCase()
		if (lower.endsWith('.whiteboard') || lower.endsWith('.excalidraw')) {
			return [baseName]
		}
		return [`${baseName}.whiteboard`, `${baseName}.excalidraw`, baseName]
	})()

	for (const candidate of candidates) {
		for (let attempt = 0; attempt < 3; attempt++) {
			const result = await page.evaluate(async ({ candidate, requestToken }) => {
				const body = new URLSearchParams({
					path: `/${candidate}`,
					shareType: '3',
					permissions: '1',
				})

				const response = await fetch('ocs/v2.php/apps/files_sharing/api/v1/shares?format=json', {
					method: 'POST',
					credentials: 'include',
					headers: {
						'OCS-APIREQUEST': 'true',
						...(requestToken ? { requesttoken: requestToken } : {}),
						'Content-Type': 'application/x-www-form-urlencoded',
						Accept: 'application/json',
					},
					body,
				})

				const text = await response.text()
				let data = null
				try {
					data = JSON.parse(text)
				} catch {
					// ignore non JSON
				}

				return {
					status: response.status,
					data,
				}
			}, { candidate, requestToken })

			const metaStatus = result?.data?.ocs?.meta?.statuscode
			const shareUrl = result?.data?.ocs?.data?.url
			if (metaStatus === 200 && typeof shareUrl === 'string') {
				return shareUrl
			}

			await page.waitForTimeout(1000)
		}
	}

	// Fallback: use the UI share sidebar to create a link
	const row = page.getByRole('row', { name: new RegExp(baseName.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')) })
	const shareButton = row.getByRole('button', { name: /Sharing options|Actions/i }).first()
	await shareButton.click()

	const createLinkButton = page.getByRole('button', { name: /create a new share link/i }).first()
	await expect(createLinkButton).toBeVisible({ timeout: 15000 })
	await createLinkButton.click()

	const copyButton = page.getByRole('button', { name: /copy public link/i }).first()
	await expect(copyButton).toBeVisible({ timeout: 15000 })
	const dataClipboard = await copyButton.getAttribute('data-clipboard-text')
	if (dataClipboard) {
		return dataClipboard
	}

	await page.context().grantPermissions(['clipboard-read', 'clipboard-write'])
	await copyButton.click()
	const clipboardText = await page.evaluate(async () => {
		try {
			return await navigator.clipboard.readText()
		} catch {
			return ''
		}
	})

	if (!clipboardText) {
		throw new Error(`Failed to create public share link for ${baseName}`)
	}
	return clipboardText
}

async function uploadTextFile(page: Page, name: string, content: string) {
	const origin = new URL(await page.url()).origin
	const userResponse = await page.request.get(`${origin}/ocs/v2.php/cloud/user?format=json`, {
		headers: { 'OCS-APIREQUEST': 'true' },
	})
	expect(userResponse.ok()).toBeTruthy()
	const userPayload = await userResponse.json().catch(() => null)
	const userId = userPayload?.ocs?.data?.id
	if (!userId) {
		throw new Error('Unable to resolve current user id')
	}

	const requestToken = await page.evaluate(() => (window as any).OC?.requestToken
		|| (document.querySelector('head meta[name="requesttoken"]') as HTMLMetaElement | null)?.content
		|| null)

	const response = await page.request.fetch(
		`${origin}/remote.php/dav/files/${encodeURIComponent(userId)}/${encodeURIComponent(name)}`,
		{
			method: 'PUT',
			headers: {
				'Content-Type': 'text/plain',
				...(requestToken ? { requesttoken: requestToken } : {}),
			},
			data: content,
		},
	)

	expect(response.ok()).toBeTruthy()
}

test.beforeEach(async ({ page }) => {
	await openFilesApp(page)
})

test('public share loads viewer in read only mode', async ({ page, browser }) => {
	test.setTimeout(120000)
	const boardName = `Shared board ${Date.now()}`

	await createWhiteboard(page, { name: boardName })
	await addTextElement(page, 'Shared marker')
	await waitForCanvas(page)

	// Wait until the file appears in the Files list to ensure backend persistence
	await openFilesApp(page)
	const fileRow = page.getByRole('row', { name: new RegExp(boardName) })
	await expect(fileRow).toBeVisible({ timeout: 30000 })

	const storedName = (await fileRow.evaluate<string | null>((row) => {
		const element = row as HTMLElement
		const dataEntry = element.getAttribute('data-entryname') || element.getAttribute('data-file')
		if (dataEntry) {
			return dataEntry
		}

		const ariaLabel = element.getAttribute('aria-label') || ''
		const ariaMatch = ariaLabel.match(/file \"([^\"]+)\"/)
		if (ariaMatch?.[1]) {
			return ariaMatch[1]
		}

		const text = element.textContent || ''
		const textMatch = text.match(/([\\w\\s.-]+\\.whiteboard|[\\w\\s.-]+\\.excalidraw)/i)
		if (textMatch?.[1]) {
			return textMatch[1]
		}
		return null
	})) ?? `${boardName}.whiteboard`

	const shareUrl = await createPublicShareLink(page, storedName)

	// Open the share link in a clean context to mimic an external visitor
	const shareContext = await browser.newContext({ storageState: undefined })
	const response = await shareContext.request.get(shareUrl)
	expect(response.ok()).toBeTruthy()

	const body = await response.text()
	expect(body.toLowerCase()).toContain('whiteboard')

	// If a JWT is present, ensure it marks the file as read only
	const jwtMatch = body.match(/"jwt"\s*:\s*"([^"]+)"/)
	const embeddedJwt = jwtMatch ? jwtMatch[1] : null
	if (jwtMatch) {
		const token = jwtMatch[1]
		const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString())
		expect(payload?.isFileReadOnly).not.toBe(false)
	}

	const sharePage = await shareContext.newPage()
	await sharePage.goto(shareUrl)
	await waitForCanvas(sharePage)

	const { fileId, jwt } = await sharePage.evaluate(() => {
		try {
			const load = (window as any).OCP?.InitialState?.loadState
			return {
				fileId: load ? Number(load('whiteboard', 'file_id')) : null,
				jwt: load ? String(load('whiteboard', 'jwt') || '') : null,
			}
		} catch {
			return { fileId: null, jwt: null }
		}
	})

	const effectiveJwt = jwt || embeddedJwt

	const attemptEdit = async () => {
		const canvas = await getCanvasForInteraction(sharePage)
		await canvas.click({ position: { x: 140, y: 140 } })
		await sharePage.keyboard.type('Read only attempt')
		await sharePage.waitForTimeout(1500)
		if (!fileId || !effectiveJwt) {
			return null
		}
		const response = await sharePage.request.get(`apps/whiteboard/${fileId}`, {
			headers: { Authorization: `Bearer ${effectiveJwt}` },
		})
		expect(response.ok()).toBeTruthy()
		const shareBody = await response.json()
		return JSON.stringify(shareBody.data)
	}

	const before = await attemptEdit()
	const after = await attemptEdit()
	if (before && after) {
		expect(after).toBe(before)
	}

	await shareContext.close()
})

test('public share for non-whiteboard does not boot whiteboard runtime', async ({ page, browser }) => {
	test.setTimeout(120000)
	const fileName = `Public text ${Date.now()}.txt`

	await uploadTextFile(page, fileName, `Hello ${Date.now()}`)
	await openFilesApp(page)

	const fileRow = page.getByRole('row', { name: new RegExp(fileName.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')) })
	await expect(fileRow).toBeVisible({ timeout: 30000 })

	const shareUrl = await createPublicShareLink(page, fileName, { allowWhiteboardFallback: false })

	const shareContext = await browser.newContext({ storageState: undefined })
	const sharePage = await shareContext.newPage()
	await sharePage.goto(shareUrl)
	await sharePage.waitForLoadState('domcontentloaded')

	const state = await sharePage.evaluate(() => {
		const load = (window as any).OCP?.InitialState?.loadState
		let fileId: number | null = null
		if (load) {
			try {
				fileId = Number(load('whiteboard', 'file_id'))
			} catch {
				fileId = null
			}
		}
		return {
			fileId,
			hasCanvas: Boolean(document.querySelector('.excalidraw__canvas')),
			hasPublicShareClass: document.body.classList.contains('whiteboard-public-share'),
		}
	})

	expect(state.fileId).toBeFalsy()
	expect(state.hasCanvas).toBe(false)
	expect(state.hasPublicShareClass).toBe(false)

	await shareContext.close()
})
