import axios, { AxiosResponse } from "axios";
import { API_ENDPOINTS, APP_AUTH_BASE_URL, APP_BASE_WEBSOCKET_URL, REQUIRED_ERROR_MESSAGE_INVALID_EMAIL, REQUIRED_MESSAGE } from "./constants.js";
import { checkParameters, getUrl, isValidEmail } from "./utils.js";
import { URLType } from "./enum.js";

interface UploadFileParams {
	files: File[];
	userEmail: string;
	fileNoLimit?: number;
}

interface DeleteImageParams {
	userEmail: string;
	fileName: string;
}
interface EligibiltyImageParams {
	storeUrl: string;
	productHandle: string;
	imageURL: string;
	productDescription: string;
}
interface HandleTryOnWebSocketParams {
	tryonId: string;
	onError?: (error: any) => void;
	onSuccess?: (data: any) => void;
	onClose?: () => void;
	onOpen?: () => void;
}
interface Products {
	productUrl: string;
	productHandle: string;
	openTryonId?: string;
	selectedProductImageUrl?: string;
}

interface HandleForLatestImageParams {
	shopDomain: string;
	products: Products[];
	selectedUserImages?: string[];
	requestSource?: string;
	callbackUrl?: string;
	openTryonId?: string;
	selectedProductImageUrl?: string;
	requestedTryonViews?: string[];
}

interface HandleTimeOutParams {
	onSuccess?: (data: any) => void;
	onError?: (error: any) => void;
	tryonId: string;
}

interface GetTryOnResultParams {
	tryonId: string;
}

class TryOn {
  #socketMap: Map<string, WebSocket> = new Map();
  #timerMap: Map<string, ReturnType<typeof setTimeout>> = new Map();
  

	#accessKey?: string;
	#urlType: URLType;
	#token?: string;
	constructor(accessKey?: string, urlType = URLType.PROD, token?: string) {
		this.#accessKey = accessKey;
		this.#urlType = urlType;
		this.#token = token;
	}

	#getHeaders(token?: string, extraHeaders: Record<string, string> = {}): Record<string, string> {
		const requestToken = token ?? this.#token;
		return {
			...extraHeaders,
			...(this.#accessKey ? { "X-Api-Key": this.#accessKey } : {}),
			...(requestToken ? { Authorization: `Bearer ${requestToken}` } : {}),
		};
	}

	async uploadFile({ files, userEmail, fileNoLimit = 2 }: UploadFileParams): Promise<string> {
		if (checkParameters(files, userEmail) === false) {
			throw new Error(REQUIRED_MESSAGE);
		}

		if (!isValidEmail(userEmail.trim())) {
			throw new Error(REQUIRED_ERROR_MESSAGE_INVALID_EMAIL);
		}
		if (fileNoLimit <= 0) {
			throw new Error(`Provide valid file number limit ${fileNoLimit}.`);
		}
		if (files?.length > fileNoLimit) {
			throw new Error(`Cannot allow more than ${fileNoLimit} files.`);
		}
		try {
			const payload: { userEmail: string; userImages: string[] } = {
				userEmail,
				userImages: [],
			};
			files?.forEach((file: File) => {
				payload.userImages.push(file.name);
			});
			const signedUrlRes = await this.#getSignedUrl(payload);
			for (const file of files) {
				await this.#s3Upload(signedUrlRes.data.uploadUrls[file.name].url, file);
			}
			return `uploaded successfully!`;
		} catch (error) {
			throw error;
		}
	}

	#getSignedUrl(payload: { userEmail: string; userImages: string[] }): Promise<AxiosResponse<any>> {
		if (checkParameters(payload) === false) {
			throw new Error(REQUIRED_MESSAGE);
		}
		return axios.post(`${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_IMAGE_UPLOAD}`, payload, {
			headers: this.#getHeaders(undefined, { "Content-Type": "application/json" }),
		});
	}

	#s3Upload(url: string, file: File): Promise<AxiosResponse<any>> {
		if (checkParameters(url, file) === false) {
			throw new Error(REQUIRED_MESSAGE);
		}
		return axios.put(url, file, {
			headers: {
				"Content-Type": file.type,
			},
		});
	}

	getUploadedFiles(userEmail: string): Promise<AxiosResponse<any>> {
		if (checkParameters(userEmail) === false) {
			throw new Error(REQUIRED_MESSAGE);
		}
		if (!isValidEmail(userEmail.trim())) {
			throw new Error(REQUIRED_ERROR_MESSAGE_INVALID_EMAIL);
		}

		const payload = {
			userEmail,
		};

		return axios.post(`${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_IMAGE_DOWNLOAD}`, payload, {
			headers: this.#getHeaders(),
		});
	}

	deleteImage({ userEmail, fileName }: DeleteImageParams): Promise<AxiosResponse<any>> {
		if (checkParameters(userEmail, fileName) === false) {
			throw new Error(REQUIRED_MESSAGE);
		}
		if (!isValidEmail(userEmail.trim())) {
			throw new Error(REQUIRED_ERROR_MESSAGE_INVALID_EMAIL);
		}
		const payload = {
			userEmail,
			file: fileName,
		};
		return axios.delete(`${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_IMAGE_URLS}`, {
			headers: this.#getHeaders(),
			data: payload,
		});
	}

  #disconnectSocket = (tryonId?: string): void => {
    if (tryonId) {
      const socket = this.#socketMap.get(tryonId);
      const timer = this.#timerMap.get(tryonId);
      socket?.close();
      if (timer) clearTimeout(timer);
      this.#socketMap.delete(tryonId);
      this.#timerMap.delete(tryonId);
    } else {
      // Disconnect all
      this.#socketMap.forEach((socket) => socket.close());
      this.#timerMap.forEach((timer) => clearTimeout(timer));
      this.#socketMap.clear();
      this.#timerMap.clear();
    }
  };
  

  #handleTimeOut = ({ onSuccess, onError, tryonId }: HandleTimeOutParams): void => {
    const timer = setTimeout(() => {
      this.#handleGetTryOnResult({ onSuccess, onError, tryonId });
      this.#disconnectSocket(tryonId);
    }, 300000);
    this.#timerMap.set(tryonId, timer);
  };
  

  handleTryOnWebSocket = ({ tryonId, onError, onSuccess, onClose, onOpen }: HandleTryOnWebSocketParams): void => {
    if (checkParameters(tryonId) === false) {
      throw new Error(REQUIRED_MESSAGE);
    }
  
    this.#disconnectSocket(tryonId);
  
    const url = `${getUrl({ urlName: APP_BASE_WEBSOCKET_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON}?tryonId=${tryonId}`;
    const socket = new WebSocket(url);
  
    this.#socketMap.set(tryonId, socket);
  
    socket.onopen = () => {
      onOpen?.();
      this.#handleTimeOut({ onSuccess, onError, tryonId });
    };
  
    socket.onmessage = (event) => {
      let data;
      try {
        data = JSON.parse(event.data);
      } catch (error) {
        console.log("Invalid JSON:", event.data);
        return;
      }
  
      if (data?.eventType === "tryon.completed") {
        onSuccess?.(data);
      } else if (data?.eventType=="tryon.failed") {
		onError?.(data);
	  }
  
      const timer = this.#timerMap.get(tryonId);
      if (timer) clearTimeout(timer);
      this.#timerMap.delete(tryonId);
    };
  
    socket.onclose = () => {
      onClose?.();
    };
  
    socket.onerror = (event) => {
      onError?.(event);
    };
  };
  

	handleTryOnSubmit({ shopDomain, products, selectedUserImages, requestSource, callbackUrl, openTryonId, selectedProductImageUrl ,requestedTryonViews}: HandleForLatestImageParams): Promise<AxiosResponse<any>> {
		if (checkParameters(shopDomain, products) === false) {
			throw new Error(REQUIRED_MESSAGE);
		}

		const payload = {
			products,
			customerStoreUrl: shopDomain,
			...(selectedUserImages !== undefined && selectedUserImages !== null && { selectedUserImages }),
			...(requestSource !== undefined && requestSource !== null && { requestSource }),
			...(callbackUrl !== undefined && callbackUrl !== null && { callbackUrl }),
			...(openTryonId !== undefined && openTryonId !== null && { openTryonId }),
			...(selectedProductImageUrl !== undefined && selectedProductImageUrl !== null && { selectedProductImageUrl }),
			...(requestedTryonViews && requestedTryonViews?.length > 0 && { requestedTryonViews }),
		};
		const url = `${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON}`;
		return axios.post(url, payload, {
			headers: this.#getHeaders(),
		});
	}

	#handleGetTryOnResult = async ({ onSuccess, onError, tryonId }: HandleTimeOutParams): Promise<void> => {
		try {
			const data = await this.getTryOnResult({ tryonId });
			onSuccess?.(data.data);
		} catch (error) {
			onError?.(error);
		}
	};

	getShareLink(tryonId: string): Promise<AxiosResponse<any>> {
		return axios.post(
			`${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_SHARE}`,
			{ tryonId },
			{
				headers: this.#getHeaders(),
			},
		);
	}

	getTryOnResult = ({ tryonId }: GetTryOnResultParams): Promise<AxiosResponse<any>> => {
		if (checkParameters(tryonId) === false) {
			throw new Error(REQUIRED_MESSAGE);
		}
		const url = `${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_RESULT_IMAGE_DOWNLOAD}/${tryonId}`;
		return axios.post(url, null, {
			headers: this.#getHeaders(),
		});
	};

	getProductImageEligibility({ storeUrl, productHandle, imageURL, productDescription }: EligibiltyImageParams) {
		if (checkParameters(storeUrl, productHandle, imageURL) === false) {
			throw new Error(REQUIRED_MESSAGE);
		}
		const payload = { storeUrl, productHandle, imageURL, productDescription: productDescription ?? null };
		const url = `${getUrl({ urlName: APP_AUTH_BASE_URL, urlType: this.#urlType })}${API_ENDPOINTS.TRY_ON_PRODUCT_IMAGE_ELIGIBILTY}`;
		return axios.post(url, payload, {
			headers: this.#getHeaders(),
		});
	}
}

export default TryOn;
