import { IncomingHttpHeaders } from 'http';

import { Request, Response } from 'express';

import { SequelizeApiPaginatePayload } from '#services/database/sequelize/types';

import { JWTPayload } from '#services/auth';
import Config from '#config';

export type ListData<T = unknown> = {
	rows: T[];

	count: number;

	pageSize: number;

	page: number;
};

export type ErrorStatusParams = {
	status: number;

	err?: Error;
};

export type OkParams = {
	data: unknown;

	statusCode?: number;
};

// Augment Express's Response so that (res: Res) is assignable to express-automatic-routes Middleware

declare global {
	namespace Express {
		interface Response {
			sendOk: (params: OkParams) => void;

			error: (err: unknown) => void;
		}
	}
}

export interface Req<T = unknown> extends Omit<Request, 'user'> {
	user?: JWTPayload;

	payload?: SequelizeApiPaginatePayload<T>;

	headers: IncomingHttpHeaders & { isadmin?: string };

	/** Express route params are always strings at runtime */

	params: Record<string, string>;
}

export interface Res extends Response {
	sendErrorStatus?: (params: ErrorStatusParams) => void;

	sendOk: (params: OkParams) => void;

	sendError?: (params: { err: Error }) => void;

	error: (err: unknown) => void;
}

export interface QueryReturnType<T = unknown> {
	rows: T[];

	count: number;

	totalPages: number;

	currentPage: number;
}

// DTOs

export type ResponseDTOParams = {
	data?: unknown;

	errors?: ErrorDTO[] | null;

	traceId?: string;
};

export const ResponseDTO = ({ data, errors, traceId }: ResponseDTOParams) => {
	const hasErrors = errors !== undefined && errors !== null && errors.length > 0;
	const response: any = {
		success: !hasErrors,
		trace_id: traceId ?? null,
		timestamp: new Date().toISOString(),
		errors: !errors ? null : errors,
	};

	if (!hasErrors && data !== undefined) {
		response.data = data;
	}

	return response;
};

export class ErrorDTO {
	field: string;

	code: string;

	messages: { vi: string; en: string };

	severity?: 'low' | 'medium' | 'high' | 'critical';

	/** Stack trace is only included if SHOW_STACK_IN_ERRORS=true in environment variables (DO NOT ENABLE IN PRODUCTION) */
	stack?: string;

	constructor(
		field: string,
		code: string,
		messages: { vi: string; en: string },
		severity?: 'low' | 'medium' | 'high' | 'critical',
		stack?: string,
	) {
		this.field = field || '';

		this.code = code;

		this.messages = messages;

		if (severity !== undefined) {
			this.severity = severity;
		}

		// Only include stack if SHOW_STACK_IN_ERRORS is enabled
		const showStack = Config.featureFlags.showStackInErrors;
		if (showStack && stack) {
			this.stack = stack;
		}
	}
}
