import { Container } from 'typedi';

import type { TestDefinition } from '@/databases/entities/test-definition.ee';
import type { User } from '@/databases/entities/user';
import type { WorkflowEntity } from '@/databases/entities/workflow-entity';
import { ProjectRepository } from '@/databases/repositories/project.repository';
import { TestDefinitionRepository } from '@/databases/repositories/test-definition.repository.ee';
import { TestRunRepository } from '@/databases/repositories/test-run.repository.ee';
import { createUserShell } from '@test-integration/db/users';
import { createWorkflow } from '@test-integration/db/workflows';
import * as testDb from '@test-integration/test-db';
import type { SuperAgentTest } from '@test-integration/types';
import * as utils from '@test-integration/utils';

let authOwnerAgent: SuperAgentTest;
let workflowUnderTest: WorkflowEntity;
let otherWorkflow: WorkflowEntity;
let testDefinition: TestDefinition;
let otherTestDefinition: TestDefinition;
let ownerShell: User;

const testServer = utils.setupTestServer({
	endpointGroups: ['workflows', 'evaluation'],
	enabledFeatures: ['feat:sharing'],
});

beforeAll(async () => {
	ownerShell = await createUserShell('global:owner');
	authOwnerAgent = testServer.authAgentFor(ownerShell);
});

beforeEach(async () => {
	await testDb.truncate(['TestDefinition', 'TestRun', 'Workflow', 'SharedWorkflow']);

	workflowUnderTest = await createWorkflow({ name: 'workflow-under-test' }, ownerShell);

	testDefinition = Container.get(TestDefinitionRepository).create({
		name: 'test',
		workflow: { id: workflowUnderTest.id },
	});
	await Container.get(TestDefinitionRepository).save(testDefinition);

	otherWorkflow = await createWorkflow({ name: 'other-workflow' });

	otherTestDefinition = Container.get(TestDefinitionRepository).create({
		name: 'other-test',
		workflow: { id: otherWorkflow.id },
	});
	await Container.get(TestDefinitionRepository).save(otherTestDefinition);
});

describe('GET /evaluation/test-definitions/:testDefinitionId/runs', () => {
	test('should retrieve empty list of runs for a test definition', async () => {
		const resp = await authOwnerAgent.get(`/evaluation/test-definitions/${testDefinition.id}/runs`);

		expect(resp.statusCode).toBe(200);
		expect(resp.body.data).toEqual([]);
	});

	test('should retrieve 404 if test definition does not exist', async () => {
		const resp = await authOwnerAgent.get('/evaluation/test-definitions/123/runs');

		expect(resp.statusCode).toBe(404);
	});

	test('should retrieve 404 if user does not have access to test definition', async () => {
		const resp = await authOwnerAgent.get(
			`/evaluation/test-definitions/${otherTestDefinition.id}/runs`,
		);

		expect(resp.statusCode).toBe(404);
	});

	test('should retrieve list of runs for a test definition', async () => {
		const testRunRepository = Container.get(TestRunRepository);
		const testRun = await testRunRepository.createTestRun(testDefinition.id);

		const resp = await authOwnerAgent.get(`/evaluation/test-definitions/${testDefinition.id}/runs`);

		expect(resp.statusCode).toBe(200);
		expect(resp.body.data).toEqual([
			expect.objectContaining({
				id: testRun.id,
				status: 'new',
				testDefinitionId: testDefinition.id,
				runAt: null,
				completedAt: null,
			}),
		]);
	});

	test('should retrieve list of runs for a test definition with pagination', async () => {
		const testRunRepository = Container.get(TestRunRepository);
		const testRun1 = await testRunRepository.createTestRun(testDefinition.id);
		// Mark as running just to make a slight delay between the runs
		await testRunRepository.markAsRunning(testRun1.id);
		const testRun2 = await testRunRepository.createTestRun(testDefinition.id);

		// Fetch the first page
		const resp = await authOwnerAgent.get(
			`/evaluation/test-definitions/${testDefinition.id}/runs?take=1`,
		);

		expect(resp.statusCode).toBe(200);
		expect(resp.body.data).toEqual([
			expect.objectContaining({
				id: testRun2.id,
				status: 'new',
				testDefinitionId: testDefinition.id,
				runAt: null,
				completedAt: null,
			}),
		]);

		// Fetch the second page
		const resp2 = await authOwnerAgent.get(
			`/evaluation/test-definitions/${testDefinition.id}/runs?take=1&skip=1`,
		);

		expect(resp2.statusCode).toBe(200);
		expect(resp2.body.data).toEqual([
			expect.objectContaining({
				id: testRun1.id,
				status: 'running',
				testDefinitionId: testDefinition.id,
				runAt: expect.any(String),
				completedAt: null,
			}),
		]);
	});
});

describe('GET /evaluation/test-definitions/:testDefinitionId/runs/:id', () => {
	test('should retrieve test run for a test definition', async () => {
		const testRunRepository = Container.get(TestRunRepository);
		const testRun = await testRunRepository.createTestRun(testDefinition.id);

		const resp = await authOwnerAgent.get(
			`/evaluation/test-definitions/${testDefinition.id}/runs/${testRun.id}`,
		);

		expect(resp.statusCode).toBe(200);
		expect(resp.body.data).toEqual(
			expect.objectContaining({
				id: testRun.id,
				status: 'new',
				testDefinitionId: testDefinition.id,
				runAt: null,
				completedAt: null,
			}),
		);
	});

	test('should retrieve 404 if test run does not exist', async () => {
		const resp = await authOwnerAgent.get(
			`/evaluation/test-definitions/${testDefinition.id}/runs/123`,
		);

		expect(resp.statusCode).toBe(404);
	});

	test('should retrieve 404 if user does not have access to test definition', async () => {
		const testRunRepository = Container.get(TestRunRepository);
		const testRun = await testRunRepository.createTestRun(otherTestDefinition.id);

		const resp = await authOwnerAgent.get(
			`/evaluation/test-definitions/${otherTestDefinition.id}/runs/${testRun.id}`,
		);

		expect(resp.statusCode).toBe(404);
	});

	test('should retrieve test run for a test definition of a shared workflow', async () => {
		const memberShell = await createUserShell('global:member');
		const memberAgent = testServer.authAgentFor(memberShell);
		const memberPersonalProject = await Container.get(
			ProjectRepository,
		).getPersonalProjectForUserOrFail(memberShell.id);

		// Share workflow with a member
		const sharingResponse = await authOwnerAgent
			.put(`/workflows/${workflowUnderTest.id}/share`)
			.send({ shareWithIds: [memberPersonalProject.id] });

		expect(sharingResponse.statusCode).toBe(200);

		// Create a test run for the shared workflow
		const testRunRepository = Container.get(TestRunRepository);
		const testRun = await testRunRepository.createTestRun(testDefinition.id);

		// Check if member can retrieve the test run of a shared workflow
		const resp = await memberAgent.get(
			`/evaluation/test-definitions/${testDefinition.id}/runs/${testRun.id}`,
		);

		expect(resp.statusCode).toBe(200);
		expect(resp.body.data).toEqual(
			expect.objectContaining({
				id: testRun.id,
			}),
		);
	});
});

describe('DELETE /evaluation/test-definitions/:testDefinitionId/runs/:id', () => {
	test('should delete test run for a test definition', async () => {
		const testRunRepository = Container.get(TestRunRepository);
		const testRun = await testRunRepository.createTestRun(testDefinition.id);

		const resp = await authOwnerAgent.delete(
			`/evaluation/test-definitions/${testDefinition.id}/runs/${testRun.id}`,
		);

		expect(resp.statusCode).toBe(200);
		expect(resp.body.data).toEqual({ success: true });

		const testRunAfterDelete = await testRunRepository.findOne({ where: { id: testRun.id } });
		expect(testRunAfterDelete).toBeNull();
	});

	test('should retrieve 404 if test run does not exist', async () => {
		const resp = await authOwnerAgent.delete(
			`/evaluation/test-definitions/${testDefinition.id}/runs/123`,
		);

		expect(resp.statusCode).toBe(404);
	});

	test('should retrieve 404 if user does not have access to test definition', async () => {
		const testRunRepository = Container.get(TestRunRepository);
		const testRun = await testRunRepository.createTestRun(otherTestDefinition.id);

		const resp = await authOwnerAgent.delete(
			`/evaluation/test-definitions/${otherTestDefinition.id}/runs/${testRun.id}`,
		);

		expect(resp.statusCode).toBe(404);
	});
});