import endpoints from '../endpoints';
import { Context } from 'cafy';
import config from '@/config/index';
import { errors as basicErrors } from './errors';
import { schemas, convertSchemaToOpenApiSchema } from './schemas';

export function genOpenapiSpec(lang = 'ja-JP') {
	const spec = {
		openapi: '3.0.0',

		info: {
			version: 'v1',
			title: 'Misskey API',
			'x-logo': { url: '/static-assets/api-doc.png' },
		},

		externalDocs: {
			description: 'Repository',
			url: 'https://github.com/misskey-dev/misskey',
		},

		servers: [{
			url: config.apiUrl,
		}],

		paths: {} as any,

		components: {
			schemas: schemas,

			securitySchemes: {
				ApiKeyAuth: {
					type: 'apiKey',
					in: 'body',
					name: 'i',
				},
			},
		},
	};

	function genProps(props: { [key: string]: Context; }) {
		const properties = {} as any;

		for (const [k, v] of Object.entries(props)) {
			properties[k] = genProp(v);
		}

		return properties;
	}

	function genProp(param: Context): any {
		const required = param.name === 'Object' ? (param as any).props ? Object.entries((param as any).props).filter(([k, v]: any) => !v.isOptional).map(([k, v]) => k) : [] : [];
		return {
			description: (param.data || {}).desc,
			default: (param.data || {}).default,
			deprecated: (param.data || {}).deprecated,
			...((param.data || {}).default ? { default: (param.data || {}).default } : {}),
			type: param.name === 'ID' ? 'string' : param.name.toLowerCase(),
			...(param.name === 'ID' ? { example: 'xxxxxxxxxx', format: 'id' } : {}),
			nullable: param.isNullable,
			...(param.name === 'String' ? {
				...((param as any).enum ? { enum: (param as any).enum } : {}),
				...((param as any).minLength ? { minLength: (param as any).minLength } : {}),
				...((param as any).maxLength ? { maxLength: (param as any).maxLength } : {}),
			} : {}),
			...(param.name === 'Number' ? {
				...((param as any).minimum ? { minimum: (param as any).minimum } : {}),
				...((param as any).maximum ? { maximum: (param as any).maximum } : {}),
			} : {}),
			...(param.name === 'Object' ? {
				...(required.length > 0 ? { required } : {}),
				properties: (param as any).props ? genProps((param as any).props) : {},
			} : {}),
			...(param.name === 'Array' ? {
				items: (param as any).ctx ? genProp((param as any).ctx) : {},
			} : {}),
		};
	}

	for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
		const porops = {} as any;
		const errors = {} as any;

		if (endpoint.meta.errors) {
			for (const e of Object.values(endpoint.meta.errors)) {
				errors[e.code] = {
					value: {
						error: e,
					},
				};
			}
		}

		if (endpoint.meta.params) {
			for (const [k, v] of Object.entries(endpoint.meta.params)) {
				if (v.validator.data == null) v.validator.data = {};
				if (v.desc) v.validator.data.desc = v.desc[lang];
				if (v.deprecated) v.validator.data.deprecated = v.deprecated;
				if (v.default) v.validator.data.default = v.default;
				porops[k] = v.validator;
			}
		}

		const required = endpoint.meta.params ? Object.entries(endpoint.meta.params).filter(([k, v]) => !v.validator.isOptional).map(([k, v]) => k) : [];

		const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};

		let desc = (endpoint.meta.desc ? endpoint.meta.desc[lang] : 'No description provided.') + '\n\n';
		desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`;
		if (endpoint.meta.kind) {
			const kind = endpoint.meta.kind;
			desc += ` / **Permission**: *${kind}*`;
		}

		const info = {
			operationId: endpoint.name,
			summary: endpoint.name,
			description: desc,
			externalDocs: {
				description: 'Source code',
				url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`,
			},
			...(endpoint.meta.tags ? {
				tags: [endpoint.meta.tags[0]],
			} : {}),
			...(endpoint.meta.requireCredential ? {
				security: [{
					ApiKeyAuth: [],
				}],
			} : {}),
			requestBody: {
				required: true,
				content: {
					'application/json': {
						schema: {
							type: 'object',
							...(required.length > 0 ? { required } : {}),
							properties: endpoint.meta.params ? genProps(porops) : {},
						},
					},
				},
			},
			responses: {
				...(endpoint.meta.res ? {
					'200': {
						description: 'OK (with results)',
						content: {
							'application/json': {
								schema: resSchema,
							},
						},
					},
				} : {
					'204': {
						description: 'OK (without any results)',
					},
				}),
				'400': {
					description: 'Client error',
					content: {
						'application/json': {
							schema: {
								$ref: '#/components/schemas/Error',
							},
							examples: { ...errors, ...basicErrors['400'] },
						},
					},
				},
				'401': {
					description: 'Authentication error',
					content: {
						'application/json': {
							schema: {
								$ref: '#/components/schemas/Error',
							},
							examples: basicErrors['401'],
						},
					},
				},
				'403': {
					description: 'Forbidden error',
					content: {
						'application/json': {
							schema: {
								$ref: '#/components/schemas/Error',
							},
							examples: basicErrors['403'],
						},
					},
				},
				'418': {
					description: 'I\'m Ai',
					content: {
						'application/json': {
							schema: {
								$ref: '#/components/schemas/Error',
							},
							examples: basicErrors['418'],
						},
					},
				},
				...(endpoint.meta.limit ? {
					'429': {
						description: 'To many requests',
						content: {
							'application/json': {
								schema: {
									$ref: '#/components/schemas/Error',
								},
								examples: basicErrors['429'],
							},
						},
					},
				} : {}),
				'500': {
					description: 'Internal server error',
					content: {
						'application/json': {
							schema: {
								$ref: '#/components/schemas/Error',
							},
							examples: basicErrors['500'],
						},
					},
				},
			},
		};

		spec.paths['/' + endpoint.name] = {
			post: info,
		};
	}

	return spec;
}