import { GenericError } from '#interfaces/error/generic';
import Config from '#config';

export interface ZaloUserInfo {
	id: string;
	name: string;
	avatar?: string;
	phone?: string;
	email?: string;
}

export interface ZaloTokenResponse {
	access_token: string;
	refresh_token?: string;
	expires_in?: number;
}

interface ZaloTokenApiResponse {
	access_token: string;
	refresh_token?: string;
	expires_in?: number;
	error?: string;
	error_description?: string;
}

interface ZaloUserInfoApiResponse {
	id: string;
	name: string;
	picture?: {
		data?: {
			url?: string;
		};
	};
	phone_number?: string;
	error?: string;
	error_description?: string;
}

/**
 * Zalo Service for Mini App authentication
 * Documentation: https://developers.zalo.me/docs/sdk/mini-app
 */
export class ZaloService {
	private static readonly ZALO_API_BASE = 'https://graph.zalo.me/v2.0';
	private static readonly ZALO_APP_ID = Config.zalo.appId;
	private static readonly ZALO_SECRET_KEY = Config.zalo.secretKey;

	/**
	 * Validate Zalo configuration
	 */
	private static validateConfig(): void {
		if (!this.ZALO_APP_ID || !this.ZALO_SECRET_KEY) {
			throw new GenericError('INTERNAL_SERVER_ERROR');
		}
	}

	/**
	 * Verify Zalo authorization code and get access token
	 * @param code - Authorization code from Zalo JS SDK
	 * @returns Zalo access token
	 */
	static async verifyZaloCode(code: string): Promise<ZaloTokenResponse> {
		this.validateConfig();

		try {
			const response = await fetch(`${this.ZALO_API_BASE}/oauth/access_token`, {
				method: 'POST',
				headers: {
					'Content-Type': 'application/x-www-form-urlencoded',
				},
				body: new URLSearchParams({
					app_id: this.ZALO_APP_ID,
					code: code,
					grant_type: 'authorization_code',
				}),
			});

			const data = (await response.json()) as ZaloTokenApiResponse;

			if (!response.ok || data.error) {
				throw new GenericError('INVALID_CREDENTIALS');
			}

			const result: ZaloTokenResponse = {
				access_token: data.access_token,
			};

			if (data.refresh_token) {
				result.refresh_token = data.refresh_token;
			}

			if (data.expires_in) {
				result.expires_in = data.expires_in;
			}

			return result;
		} catch (error) {
			if (error instanceof GenericError) throw error;
			throw new GenericError('INVALID_CREDENTIALS');
		}
	}

	/**
	 * Get Zalo user info using access token
	 * @param accessToken - Zalo access token
	 * @returns Zalo user information
	 */
	static async getZaloUserInfo(accessToken: string): Promise<ZaloUserInfo> {
		this.validateConfig();

		try {
			const response = await fetch(`${this.ZALO_API_BASE}/me?fields=id,name,picture,phone_number`, {
				method: 'GET',
				headers: {
					access_token: accessToken,
				},
			});

			const data = (await response.json()) as ZaloUserInfoApiResponse;

			if (!response.ok || data.error) {
				throw new GenericError('UNAUTHORIZED');
			}

			const userInfo: ZaloUserInfo = {
				id: data.id,
				name: data.name,
			};

			if (data.picture?.data?.url) {
				userInfo.avatar = data.picture.data.url;
			}

			if (data.phone_number) {
				userInfo.phone = data.phone_number;
			}

			return userInfo;
		} catch (error) {
			if (error instanceof GenericError) throw error;
			throw new GenericError('UNAUTHORIZED');
		}
	}

	/**
	 * Verify Zalo Open ID (alternative to authorization code flow)
	 * @param openId - Zalo Open ID from JS SDK
	 * @returns Zalo user info from Zalo server
	 */
	static async verifyZaloOpenId(openId: string): Promise<ZaloUserInfo> {
		this.validateConfig();

		try {
			// Call Zalo API to verify Open ID and get user info
			// Endpoint: https://graph.zalo.me/v2.0/me with open_id verification
			const response = await fetch(
				`${this.ZALO_API_BASE}/me?fields=id,name,picture,phone_number&open_id=${encodeURIComponent(openId)}`,
				{
					method: 'GET',
					headers: {
						app_id: this.ZALO_APP_ID,
						secret_key: this.ZALO_SECRET_KEY,
					},
				},
			);

			const data = (await response.json()) as ZaloUserInfoApiResponse;

			if (!response.ok || data.error) {
				throw new GenericError('INVALID_CREDENTIALS');
			}

			const userInfo: ZaloUserInfo = {
				id: data.id,
				name: data.name,
			};

			if (data.picture?.data?.url) {
				userInfo.avatar = data.picture.data.url;
			}

			if (data.phone_number) {
				userInfo.phone = data.phone_number;
			}

			return userInfo;
		} catch (error) {
			if (error instanceof GenericError) throw error;
			throw new GenericError('INVALID_CREDENTIALS');
		}
	}
}

export default ZaloService;
