import { afterEach, describe, expect, it } from 'bun:test'
import type { BackendValidationSettings } from '../../core/backendValidation'
import { BackendPreviewClient } from './backendPreview'

const ORIGINAL_FETCH = globalThis.fetch

afterEach(() => {
	globalThis.fetch = ORIGINAL_FETCH
})

describe('BackendPreviewClient', () => {
	it('updates an existing seeded script on path conflict and waits for deployment', async () => {
		const requests: Array<{ url: string; init?: RequestInit }> = []
		globalThis.fetch = mockFetch(
			requests,
			textResponse(200, 'token'),
			textResponse(200, ''),
			textResponse(400, 'Path conflict for f/evals/add_two_numbers with non-archived hash 123'),
			jsonResponse(200, { hash: '123' }),
			textResponse(200, '456'),
			jsonResponse(200, { lock: 'script.lock', lock_error_logs: null })
		)

		const client = new BackendPreviewClient(
			buildSettings({ baseUrl: 'http://backend.test/script-upsert' })
		)

		await client.createScript({
			workspaceId: 'test',
			path: 'f/evals/add_two_numbers',
			summary: 'Add two numbers',
			content: 'export async function main(a: number, b: number) { return a + b }',
			language: 'bun'
		})

		expect(requests.map((entry) => entry.url)).toEqual([
			'http://backend.test/script-upsert/api/auth/login',
			'http://backend.test/script-upsert/api/w/test/folders/create',
			'http://backend.test/script-upsert/api/w/test/scripts/create',
			'http://backend.test/script-upsert/api/w/test/scripts/get/p/f/evals/add_two_numbers',
			'http://backend.test/script-upsert/api/w/test/scripts/create',
			'http://backend.test/script-upsert/api/w/test/scripts/deployment_status/h/456'
		])

		const updateRequest = requests[4]
		expect(updateRequest.init?.method).toBe('POST')
		expect(JSON.parse(String(updateRequest.init?.body))).toMatchObject({
			path: 'f/evals/add_two_numbers',
			parent_hash: '123',
			language: 'bun'
		})
	})

	it('updates an existing seeded flow on create conflict', async () => {
		const requests: Array<{ url: string; init?: RequestInit }> = []
		globalThis.fetch = mockFetch(
			requests,
			textResponse(200, 'token'),
			textResponse(200, ''),
			textResponse(400, 'Flow f/evals/add_numbers_flow already exists'),
			textResponse(200, '')
		)

		const client = new BackendPreviewClient(
			buildSettings({ baseUrl: 'http://backend.test/flow-upsert' })
		)

		await client.createFlow({
			workspaceId: 'test',
			path: 'f/evals/add_numbers_flow',
			summary: 'Add numbers',
			value: { modules: [] }
		})

		expect(requests.map((entry) => entry.url)).toEqual([
			'http://backend.test/flow-upsert/api/auth/login',
			'http://backend.test/flow-upsert/api/w/test/folders/create',
			'http://backend.test/flow-upsert/api/w/test/flows/create',
			'http://backend.test/flow-upsert/api/w/test/flows/update/f/evals/add_numbers_flow'
		])

		const updateRequest = requests[3]
		expect(updateRequest.init?.method).toBe('POST')
		expect(JSON.parse(String(updateRequest.init?.body))).toMatchObject({
			path: 'f/evals/add_numbers_flow',
			value: { modules: [] }
		})
	})

	it('serializes shared-workspace validations inside the overridden workspace', async () => {
		globalThis.fetch = async (input) => {
			const url = String(input)
			if (url.endsWith('/api/auth/login')) {
				return textResponse(200, 'token')
			}
			if (url.endsWith('/api/workspaces/exists')) {
				return textResponse(200, 'true')
			}
			if (url.endsWith('/api/w/shared-preview/flows/list_paths')) {
				return jsonResponse(200, [])
			}
			if (url.endsWith('/api/w/shared-preview/scripts/list_paths')) {
				return jsonResponse(200, [])
			}
			throw new Error(`Unexpected fetch: ${url}`)
		}

		const client = new BackendPreviewClient(
			buildSettings({
				baseUrl: 'http://backend.test/shared-lock',
				workspaceOverride: 'shared-preview'
			})
		)

		const order: string[] = []
		let releaseFirst: (() => void) | undefined
		let notifyFirstStart: (() => void) | undefined
		const firstStarted = new Promise<void>((resolve) => {
			notifyFirstStart = resolve
		})

		const first = client.withWorkspace('flow-test1', 1, async () => {
			order.push('first:start')
			notifyFirstStart?.()
			await new Promise<void>((resolve) => {
				releaseFirst = resolve
			})
			order.push('first:end')
		})

		const second = client.withWorkspace('flow-test2', 1, async () => {
			order.push('second:start')
			order.push('second:end')
		})

		await firstStarted
		expect(order).toEqual(['first:start'])

		releaseFirst?.()
		await Promise.all([first, second])

		expect(order).toEqual(['first:start', 'first:end', 'second:start', 'second:end'])
	})

	it('clears managed shared-workspace assets before preview runs', async () => {
		const requests: Array<{ url: string; init?: RequestInit }> = []
		globalThis.fetch = mockFetch(
			requests,
			textResponse(200, 'token'),
			textResponse(200, 'true'),
			jsonResponse(200, ['f/evals/old_subflow', 'u/admin/keep_flow']),
			textResponse(200, ''),
			jsonResponse(200, ['f/evals/old_script', 'f/shared/keep_script']),
			textResponse(200, '')
		)

		const client = new BackendPreviewClient(
			buildSettings({
				baseUrl: 'http://backend.test/shared-cleanup',
				workspaceOverride: 'shared-preview'
			})
		)

		await client.withWorkspace('flow-test1', 1, async () => undefined)

		expect(requests.map((entry) => entry.url)).toEqual([
			'http://backend.test/shared-cleanup/api/auth/login',
			'http://backend.test/shared-cleanup/api/workspaces/exists',
			'http://backend.test/shared-cleanup/api/w/shared-preview/flows/list_paths',
			'http://backend.test/shared-cleanup/api/w/shared-preview/flows/delete/f/evals/old_subflow',
			'http://backend.test/shared-cleanup/api/w/shared-preview/scripts/list_paths',
			'http://backend.test/shared-cleanup/api/w/shared-preview/scripts/delete/p/f/evals/old_script'
		])
	})

	it('retries login after a cached login failure', async () => {
		const requests: Array<{ url: string; init?: RequestInit }> = []
		globalThis.fetch = mockFetch(
			requests,
			textResponse(503, 'backend starting'),
			textResponse(200, 'token'),
			textResponse(200, 'true'),
			jsonResponse(200, []),
			jsonResponse(200, [])
		)

		const client = new BackendPreviewClient(
			buildSettings({
				baseUrl: 'http://backend.test/login-retry',
				workspaceOverride: 'shared-preview'
			})
		)

		await expect(client.withWorkspace('flow-test1', 1, async () => undefined)).rejects.toThrow(
			'login for backend validation failed'
		)
		await expect(client.withWorkspace('flow-test1', 1, async () => 'ok')).resolves.toBe('ok')

		expect(
			requests.filter((entry) => entry.url === 'http://backend.test/login-retry/api/auth/login')
		).toHaveLength(2)
	})
})

function buildSettings(
	overrides: Partial<BackendValidationSettings> = {}
): BackendValidationSettings {
	return {
		mode: 'preview',
		baseUrl: 'http://backend.test/default',
		email: 'admin@windmill.dev',
		password: 'changeme',
		pollIntervalMs: 1,
		maxWaitMs: 50,
		...overrides
	}
}

function mockFetch(
	requests: Array<{ url: string; init?: RequestInit }>,
	...responses: Response[]
): typeof fetch {
	const queue = [...responses]
	return async (input, init) => {
		const url = String(input)
		requests.push({ url, init })
		const next = queue.shift()
		if (!next) {
			throw new Error(`Unexpected fetch: ${url}`)
		}
		return next
	}
}

function jsonResponse(status: number, body: unknown): Response {
	return new Response(JSON.stringify(body), {
		status,
		headers: { 'Content-Type': 'application/json' }
	})
}

function textResponse(status: number, body: string): Response {
	return new Response(body, { status })
}
