import { it, describe, afterEach, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { AiAssistantClient } from '../src/index.ts';
import { faker } from '@faker-js/faker';
import nock from 'nock';
import { ASK_AI_PAYLOAD, INIT_AI_ASSISTANT_WITH_CODE_NODE_PAYLOAD } from './testData.ts';

const BASE_URL = 'https://ai-assistant.n8n.io';
const LICENSE_CERT = 'test';
const N8N_VERSION = '1.0.0';
const USER_ID = '1';
const CONSUMER_ID = 'test';

const client = new AiAssistantClient({
	licenseCert: LICENSE_CERT,
	consumerId: CONSUMER_ID,
	n8nVersion: N8N_VERSION,
	baseUrl: BASE_URL,
});

const accessToken = faker.string.uuid();

describe('AiAssistantClient', () => {
	afterEach(() => {
		nock.cleanAll();
	});

	beforeEach(() => {
		if (!nock.isActive()) nock.activate();
	});

	it('Should call POST /auth/token on first request', async () => {
		// Arrange

		let newTokenRequestBody: any;

		const newTokenRequest = nock(BASE_URL)
			.post('/auth/token')
			.reply(function (_, requestBody) {
				newTokenRequestBody = requestBody;
				return [
					200,
					{
						accessToken,
						tokenType: 'Bearer',
					},
				];
			});

		nock(BASE_URL)
			.post('/v1/chat')
			.reply(200, {
				body: {
					sessionId: faker.string.uuid(),
					messages: [],
				},
			});

		// Act
		const response = await client.chat(INIT_AI_ASSISTANT_WITH_CODE_NODE_PAYLOAD, { id: USER_ID });

		// Assert
		assert.ok(newTokenRequest.isDone());
		assert.ok(response.body instanceof ReadableStream);
		assert.ok('licenseCert' in newTokenRequestBody);
		assert.ok(newTokenRequestBody.licenseCert === LICENSE_CERT);
	});

	it('Should call POST /v1/chat after POST /auth/token with valid parameters', async () => {
		// Arrange

		let chatRequestBody: any;
		let askAiRequestHeaders: any;

		nock(BASE_URL)
			.post('/auth/token')
			.once()
			.reply(function () {
				return [
					200,
					{
						accessToken,
						tokenType: 'Bearer',
					},
				];
			});

		const chatRequest = nock(BASE_URL)
			.post('/v1/chat')
			.once()
			.reply(function (_, requestBody) {
				askAiRequestHeaders = this.req.getHeaders();
				chatRequestBody = requestBody;
				return [
					200,
					{
						sessionId: faker.string.uuid(),
						messages: [],
					},
				];
			});

		// Act
		const response = await client.chat(INIT_AI_ASSISTANT_WITH_CODE_NODE_PAYLOAD, { id: USER_ID });

		// Assert
		assert.ok(chatRequest.isDone());
		assert.ok(response.body instanceof ReadableStream);
		assert.deepStrictEqual(chatRequestBody, INIT_AI_ASSISTANT_WITH_CODE_NODE_PAYLOAD);
		assert.ok('authorization' in askAiRequestHeaders);
		assert.ok('x-consumer-id' in askAiRequestHeaders);
		assert.ok('x-n8n-version' in askAiRequestHeaders);
		assert.ok('x-sdk-version' in askAiRequestHeaders);
		assert.ok('x-user-id' in askAiRequestHeaders);
		assert.ok(askAiRequestHeaders.authorization === `Bearer ${accessToken}`);
		assert.ok(askAiRequestHeaders['x-n8n-version'] === N8N_VERSION);
		assert.ok(askAiRequestHeaders['x-user-id'] === USER_ID);
		assert.ok(askAiRequestHeaders['x-consumer-id'] === CONSUMER_ID);
	});

	it('Should call POST /auth/token when token is expired', async () => {
		// Arrange

		let chatRequestBody: any;
		let askAiRequestHeaders: any;

		nock(BASE_URL)
			.post('/auth/token')
			.once()
			.reply(function () {
				return [
					200,
					{
						accessToken,
						tokenType: 'Bearer',
					},
				];
			});

		nock(BASE_URL)
			.post('/v1/chat')
			.twice()
			.reply(function (_, requestBody) {
				askAiRequestHeaders = this.req.getHeaders();
				chatRequestBody = requestBody;
				return [
					200,
					{
						sessionId: faker.string.uuid(),
						messages: [],
					},
				];
			});

		// Act
		await client.chat(INIT_AI_ASSISTANT_WITH_CODE_NODE_PAYLOAD, { id: USER_ID });

		nock(BASE_URL)
			.post('/auth/token')
			.once()
			.reply(function () {
				return [401];
			});

		const response = await client.chat(INIT_AI_ASSISTANT_WITH_CODE_NODE_PAYLOAD, { id: USER_ID });

		// Assert
		assert.ok(response.body instanceof ReadableStream);
		assert.deepStrictEqual(chatRequestBody, INIT_AI_ASSISTANT_WITH_CODE_NODE_PAYLOAD);
		assert.ok('authorization' in askAiRequestHeaders);
		assert.ok('x-consumer-id' in askAiRequestHeaders);
		assert.ok('x-n8n-version' in askAiRequestHeaders);
		assert.ok('x-sdk-version' in askAiRequestHeaders);
		assert.ok('x-user-id' in askAiRequestHeaders);
		assert.ok(askAiRequestHeaders.authorization === `Bearer ${accessToken}`);
		assert.ok(askAiRequestHeaders['x-n8n-version'] === N8N_VERSION);
		assert.ok(askAiRequestHeaders['x-user-id'] === USER_ID);
		assert.ok(askAiRequestHeaders['x-consumer-id'] === CONSUMER_ID);
	});

	it('Should call POST /v1/ask-ai after POST /auth/token with valid parameters', async () => {
		// Arrange

		let askAiRequestBody: any;
		let askAiRequestHeaders: any;

		const code = "console.log('Hello, World!');";

		nock(BASE_URL)
			.post('/auth/token')
			.once()
			.reply(function () {
				return [
					200,
					{
						accessToken,
						tokenType: 'Bearer',
					},
				];
			});

		const chatRequest = nock(BASE_URL)
			.post('/v1/ask-ai')
			.once()
			.reply(function (_, requestBody) {
				askAiRequestHeaders = this.req.getHeaders();
				askAiRequestBody = requestBody;
				return [
					200,
					{
						code,
					},
				];
			});

		// Act

		const response = await client.askAi(ASK_AI_PAYLOAD, { id: USER_ID });

		// Assert
		assert.ok(chatRequest.isDone());
		assert.deepStrictEqual(askAiRequestBody, ASK_AI_PAYLOAD);
		assert.ok('authorization' in askAiRequestHeaders);
		assert.ok('x-consumer-id' in askAiRequestHeaders);
		assert.ok('x-n8n-version' in askAiRequestHeaders);
		assert.ok('x-sdk-version' in askAiRequestHeaders);
		assert.ok('x-user-id' in askAiRequestHeaders);
		assert.ok(askAiRequestHeaders.authorization === `Bearer ${accessToken}`);
		assert.ok(askAiRequestHeaders['x-n8n-version'] === N8N_VERSION);
		assert.ok(askAiRequestHeaders['x-user-id'] === USER_ID);
		assert.ok(askAiRequestHeaders['x-consumer-id'] === CONSUMER_ID);
		assert.ok(response.code === code);
	});

	it('Should throw InternalServerError when /ask-ai API returns error response', async () => {
		nock(BASE_URL)
			.post('/auth/token')
			.once()
			.reply(function () {
				return [
					200,
					{
						accessToken,
						tokenType: 'Bearer',
					},
				];
			});

		nock(BASE_URL)
			.post('/v1/ask-ai')
			.once()
			.reply(function () {
				return [500, {}];
			});

		assert.rejects(
			client.askAi(ASK_AI_PAYLOAD, { id: USER_ID }),
			new RegExp('AIServiceAPIResponseError: Internal Server Error'),
		);
	});

	it('Should throw AuthError when cannot auth to call /ask-ai', async () => {
		nock(BASE_URL)
			.post('/auth/token')
			.once()
			.reply(function () {
				return [
					500,
					{
						message: 'test',
					},
				];
			});

		nock(BASE_URL)
			.post('/v1/ask-ai')
			.once()
			.reply(function () {
				return [200, {}];
			});

		assert.rejects(
			client.askAi(ASK_AI_PAYLOAD, { id: USER_ID }),
			new RegExp('AIServiceAPIResponseError: Invalid response from assistant service'),
		);
	});

	it('generateAiCreditsCredentials should call POST /ai-credits/credentials', async () => {
		// Arrange

		let aiCreditCredentialsRequestBody: any;

		const aiCreditCredentialsRequest = nock(BASE_URL)
			.post('/v1/ai-credits/credentials')
			.reply(function (_, requestBody) {
				aiCreditCredentialsRequestBody = requestBody;
				return [
					200,
					{
						apiKey: faker.string.uuid(),
						url: faker.internet.url(),
					},
				];
			});

		// Act
		await client.generateAiCreditsCredentials({ id: USER_ID });

		// Assert
		assert.ok(aiCreditCredentialsRequest.isDone());
		assert.ok('licenseCert' in aiCreditCredentialsRequestBody);
	});

	it('generateApiProxyCredentials should call POST /api-proxy/credentials', async () => {
		// Arrange
		const expectedApiKey = faker.string.uuid();
		let apiProxyCredentialsRequestBody: any;
		let apiProxyCredentialsRequestHeaders: any;

		const apiProxyCredentialsRequest = nock(BASE_URL)
			.post('/v1/api-proxy/credentials')
			.reply(function (_, requestBody) {
				apiProxyCredentialsRequestBody = requestBody;
				apiProxyCredentialsRequestHeaders = this.req.getHeaders();
				return [
					200,
					{
						apiKey: expectedApiKey,
					},
				];
			});

		// Act
		const response = await client.generateApiProxyCredentials({ id: USER_ID });

		// Assert
		assert.ok(apiProxyCredentialsRequest.isDone());
		assert.ok('licenseCert' in apiProxyCredentialsRequestBody);
		assert.strictEqual(apiProxyCredentialsRequestBody.licenseCert, LICENSE_CERT);
		assert.ok('x-consumer-id' in apiProxyCredentialsRequestHeaders);
		assert.ok('x-user-id' in apiProxyCredentialsRequestHeaders);
		assert.ok('x-n8n-version' in apiProxyCredentialsRequestHeaders);
		assert.ok('x-sdk-version' in apiProxyCredentialsRequestHeaders);
		assert.strictEqual(apiProxyCredentialsRequestHeaders['x-user-id'], USER_ID);
		assert.strictEqual(apiProxyCredentialsRequestHeaders['x-consumer-id'], CONSUMER_ID);
		assert.strictEqual(apiProxyCredentialsRequestHeaders['x-n8n-version'], N8N_VERSION);
		assert.strictEqual(response.apiKey, expectedApiKey);
	});

	it('generateApiProxyCredentials should handle errors properly', async () => {
		// Arrange
		nock(BASE_URL)
			.post('/v1/api-proxy/credentials')
			.reply(400, {
				message: 'Invalid license: Missing required feature',
			});

		// Act & Assert
		await assert.rejects(
			client.generateApiProxyCredentials({ id: USER_ID }),
			new RegExp('AIServiceAPIResponseError: Invalid license: Missing required feature')
		);
	});
});
