import express from 'express';
import { Container } from 'typedi';

import { Logger } from '@/Logger';
import * as ResponseHelper from '@/ResponseHelper';
import type { ILicensePostResponse, ILicenseReadResponse } from '@/Interfaces';
import { LicenseService } from './License.service';
import { License } from '@/License';
import type { AuthenticatedRequest, LicenseRequest } from '@/requests';
import { InternalHooks } from '@/InternalHooks';
import { UnauthorizedError } from '@/errors/response-errors/unauthorized.error';
import { BadRequestError } from '@/errors/response-errors/bad-request.error';

export const licenseController = express.Router();

const OWNER_ROUTES = ['/activate', '/renew'];

/**
 * Owner checking
 */
licenseController.use((req: AuthenticatedRequest, res, next) => {
	if (OWNER_ROUTES.includes(req.path) && req.user) {
		if (!req.user.isOwner) {
			Container.get(Logger).info('Non-owner attempted to activate or renew a license', {
				userId: req.user.id,
			});
			ResponseHelper.sendErrorResponse(
				res,
				new UnauthorizedError('Only an instance owner may activate or renew a license'),
			);
			return;
		}
	}
	next();
});

/**
 * GET /license
 * Get the license data, usable by everyone
 */
licenseController.get(
	'/',
	ResponseHelper.send(async (): Promise<ILicenseReadResponse> => {
		return LicenseService.getLicenseData();
	}),
);

/**
 * POST /license/activate
 * Only usable by the instance owner, activates a license.
 */
licenseController.post(
	'/activate',
	ResponseHelper.send(async (req: LicenseRequest.Activate): Promise<ILicensePostResponse> => {
		// Call the license manager activate function and tell it to throw an error
		const license = Container.get(License);
		try {
			await license.activate(req.body.activationKey);
		} catch (e) {
			const error = e as Error & { errorId?: string };

			let message = 'Failed to activate license';

			//override specific error messages (to map License Server vocabulary to n8n terms)
			switch (error.errorId ?? 'UNSPECIFIED') {
				case 'SCHEMA_VALIDATION':
					message = 'Activation key is in the wrong format';
					break;
				case 'RESERVATION_EXHAUSTED':
					message =
						'Activation key has been used too many times. Please contact sales@n8n.io if you would like to extend it';
					break;
				case 'RESERVATION_EXPIRED':
					message = 'Activation key has expired';
					break;
				case 'NOT_FOUND':
				case 'RESERVATION_CONFLICT':
					message = 'Activation key not found';
					break;
				case 'RESERVATION_DUPLICATE':
					message = 'Activation key has already been used on this instance';
					break;
				default:
					message += `: ${error.message}`;
					Container.get(Logger).error(message, { stack: error.stack ?? 'n/a' });
			}

			throw new BadRequestError(message);
		}

		// Return the read data, plus the management JWT
		return {
			managementToken: license.getManagementJwt(),
			...(await LicenseService.getLicenseData()),
		};
	}),
);

/**
 * POST /license/renew
 * Only usable by instance owner, renews a license
 */
licenseController.post(
	'/renew',
	ResponseHelper.send(async (): Promise<ILicensePostResponse> => {
		// Call the license manager activate function and tell it to throw an error
		const license = Container.get(License);
		try {
			await license.renew();
		} catch (e) {
			const error = e as Error & { errorId?: string };

			// not awaiting so as not to make the endpoint hang
			void Container.get(InternalHooks).onLicenseRenewAttempt({ success: false });
			if (error instanceof Error) {
				throw new BadRequestError(error.message);
			}
		}

		// not awaiting so as not to make the endpoint hang
		void Container.get(InternalHooks).onLicenseRenewAttempt({ success: true });

		// Return the read data, plus the management JWT
		return {
			managementToken: license.getManagementJwt(),
			...(await LicenseService.getLicenseData()),
		};
	}),
);
