import UIComponent from "sap/ui/core/UIComponent";
import Device from "sap/ui/Device";
import ExceptionHandler from "./model/ts/handlers/ExceptionHandler";
import LogHandler from "./model/ts/handlers/LogHandler";
import AuthentificationHandler from "./model/ts/handlers/AuthentificationHandler";
import User from "./model/ts/User";
import Menu from "./model/ts/Menu";
import IconPool from "sap/ui/core/IconPool";
import ODataModel from "sap/ui/model/odata/v4/ODataModel";
import JSONModel from "sap/ui/model/json/JSONModel";
import BindingMode from "sap/ui/model/BindingMode";
import LabelTranslator from "./model/ts/translation/LabelTranslator";
import FieldTranslator from "./model/ts/translation/FieldTranslator";
import Storage from "sap/ui/util/Storage";
/**
 * @namespace sap.mcsuite
 */
export default class Component extends UIComponent {
	public static metadata = {
		manifest: "json",
		interfaces: ["sap.ui.core.IAsyncContentCreation"],
		rootView: "view.Main",
		handleValidation: true,
	};

	translator = {
		Label: new LabelTranslator(this),
		Field: new FieldTranslator(this)
	};
	private session : Storage;
	private version: string = "1.0.0";
	private buildDate: string = "2024-06-01T12:00:00Z";
	private phpBackendURL: string;
	private keycloakURL: string;
	private realm: string;
	private clientId: string;
	private odataURL: string;
	private contentDensityClass: string;
	private baseHash: string;
	private errorHandler: ExceptionHandler;
	private logHandler: LogHandler;
	private authentificationHandler: AuthentificationHandler;
	private user: User;
	private menu: Menu;
	private static readonly FONT_DIR: string = "assets/fa-name-unicode-mapping/";
	private static readonly ODATA_SIZE_LIMIT: number = 10000;
	private configPromise: Promise<any>;

	public init(): void {
		// call the base component's init function
		super.init();
		// Load runtime configuration first
		
		
		this.user = User.getInstance();
		this.baseHash = this.getRouter().getHashChanger().getHash();
		this.configPromise = this.loadRuntimeConfig().then((config) => {
			this.phpBackendURL = config.backendUrl;
			this.keycloakURL = config.keycloakUrl;
			this.realm = config.realm;
			this.clientId = config.clientId;
			this.odataURL = config.odataUrl;
			this.version = config.version;
			this.buildDate = config.buildDate;
			this.authentificationHandler = new AuthentificationHandler(this);
			const oDeviceModel = new JSONModel(Device);
			oDeviceModel.setDefaultBindingMode(BindingMode.OneWay);
			this.setModel(oDeviceModel, "device");
			this.setModel(new JSONModel({
				version: this.version,
				buildDate: new Date(this.buildDate).toLocaleDateString("es-ES", {
					day: '2-digit',
					month: '2-digit',
					year: 'numeric'
				}),
			}), "appInfo");
			this.setODataModel();
			this.authentificationHandler.initialize();
			this.registerFontAwesomeIcons();
		});
		this.menu = Menu.getInstance();
	}

	/**
	 * Loads runtime configuration from config.json endpoint
	 * @private
	 * @returns Promise that resolves to configuration object
	 */
	private loadRuntimeConfig(): Promise<any> {
		// Add timestamp to prevent caching
		const timestamp = Date.now();
		return fetch(`./config.json?v=${timestamp}`, {
			method: 'GET',
			headers: {
				'Cache-Control': 'no-cache, no-store, must-revalidate',
				'Pragma': 'no-cache',
				'Expires': '0'
			}
		})
			.then(response => {
				if (!response.ok) {
					throw new Error(`Failed to load config: ${response.status} ${response.statusText}`);
				}
				return response.text().then(text => {
					LogHandler.display('Raw config response:', text);
					try {
						return JSON.parse(text);
					} catch (e) {
						console.error('Failed to parse config JSON:', e);
						throw new Error('Invalid JSON in config response');
					}
				});
			})
			.then(config => {
				LogHandler.display('Loaded configuration:', config);
				return config;
			})
			.catch(error => {
				console.error('Error loading runtime config:', error);
				// Return default fallback values
				const fallback = {
					backendUrl: '__BACKEND_URL__',
					keycloakUrl: '__KEYCLOAK_URL__',
					realm: '__REALM__',
					clientId: '__CLIENT_ID__',
					odataURL: '__ODATA_URL__'
				};
				LogHandler.display('Using fallback config:', fallback);
				return fallback;
			});
	}

	/**
	 * Returns a promise that resolves when configuration is loaded
	 * @public
	 * @returns Promise that resolves when config is ready
	 */
	public whenConfigReady(): Promise<any> {
		return this.configPromise;
	}

		/**
		 * Initializes the OData model for the component.
		 * This method sets the OData model with a specific service URL and size limit.
		 */
		private setODataModel(): void {
			this.setUpInterceptors();
			const oDataModel = new ODataModel({
				serviceUrl: this.odataURL,
				odataVersion: "4.0",
				autoExpandSelect: true,
				earlyRequests: false,
				operationMode: "Server",
				groupId: "$auto",
				groupProperties: {
					UpdateGroup1: { submit: "API" },
					UpdateGroup2: { submit: "API" },
					UpdateGroup3: { submit: "API" },
					UpdateGroup4: { submit: "API" },
					defaultUpdateGroup: { submit: "API" },
				},
				synchronizationMode: "None",
			});
			this.setModel(oDataModel);
			this.getODataModel().setSizeLimit(Component.ODATA_SIZE_LIMIT);
		}

		private setUpInterceptors(): void {
			const originalOpen = XMLHttpRequest.prototype.open;
			const originalSend = XMLHttpRequest.prototype.send;
			const component = this;
			// Set up any necessary interceptors for OData requests here
			XMLHttpRequest.prototype.open = function (method: string, url: string | URL | null | undefined, ...args: any[]): void {
				(this as any)._url = url;
				(this as any)._method = method;
				return originalOpen.apply(this, [method, url, ...args] as any);
			};
			XMLHttpRequest.prototype.send = function (body?: Document | XMLHttpRequestBodyInit | null): void {
				const url = (this as any)._url;
        		const method = (this as any)._method;
        		if (url && url.toString().includes(component.odataURL)) {
            		const token = component.user.getToken();
					if (token) {
						this.setRequestHeader("Authorization", `Bearer ${token}`);
						LogHandler.display(`Injecting token for ${method} ${url}`);
					}
				}
				return originalSend.apply(this, [body] as any);
            }
        }
		/**
		 * Retrieves the OData model of the component.
		 * @returns The OData model of the component
		 * @throws Error if the OData model is not set or is not an instance of ODataModel
		 */
		public getODataModel(): ODataModel {
			const oModel = this.getModel();
			if (!oModel) {
				throw new Error("OData model is not set in the component.");
			}
			if (!(oModel instanceof ODataModel)) {
				throw new Error("The model is not an instance of ODataModel.");
			}
			return oModel;
		}

	/**
	 * This method can be called to determine whether the sapUiSizeCompact or sapUiSizeCozy
	 * design mode class should be set, which influences the size appearance of some controls.
	 * @public
	 * @returns css class, either 'sapUiSizeCompact' or 'sapUiSizeCozy' - or an empty string if no css class should be set
	 */
	public getContentDensityClass(): string {
		if (this.contentDensityClass === undefined) {
			// check whether FLP has already set the content density class; do nothing in this case
			if (
				document.body.classList.contains("sapUiSizeCozy") ||
				document.body.classList.contains("sapUiSizeCompact")
			) {
				this.contentDensityClass = "";
			} else if (!Device.support.touch) {
				// apply "compact" mode if touch is not supported
				this.contentDensityClass = "sapUiSizeCompact";
			} else {
				// "cozy" in case of touch support; default for most sap.m controls, but needed for desktop-first controls like sap.ui.table.Table
				this.contentDensityClass = "sapUiSizeCozy";
			}
		}
		return this.contentDensityClass;
	}

	/*
	 * Hook for onAuthSuccess.
	 */
	public getAuth(): AuthentificationHandler {
		return this.authentificationHandler;
	}

	/**
	 * Retrieves the Keycloak URL for the component.
	 * @returns The Keycloak URL
	 */
	public getKeycloakURL(): string {
		return this.keycloakURL;
	}

	/**
	 * Retrieves the Realm for the component.
	 * @returns The Realm
	 */
	public getRealm(): string {
		return this.realm;
	}

	/**
	 * Retrieves the Client ID for the component.
	 * @returns The Client ID
	 */
	public getClientId(): string {
		return this.clientId;
	}
	/**
	 * Retrieves the Backend URL for the component.
	 * @returns The Backend URL
	 */
	public getBackendURL(): string {
		return this.phpBackendURL;
	}

	/**
	 * Registers Font Awesome icons with the IconPool.
	 * This method registers multiple Font Awesome font files to be used within the application.
	 * It iterates over a predefined list of font files and registers each one with the IconPool.
	 * The fonts are registered with their respective collection names and font families.
	 * The font files are expected to be located in the "thirdparty/webfonts/" directory.
	 * This method is called during the component initialization to ensure that the icons are available for use in the application.
	 * @private
	 */
	private registerFontAwesomeIcons(): void {
		const fontFiles = [
			"fa-brands-400.json",
			"fa-regular-400.json",
			"fa-solid-900.json",
			"fa-thin-100.json",
			"fa-light-300.json",
			"fa-duotone-900.json",
			"fa-sharp-solid-900.json",
			"fa-sharp-regular-400.json",
			"fa-sharp-light-300.json",
			"fa-sharp-thin-100.json",
			"fa-sharp-duotone-solid-900.json",
		];
		// Register each font
		fontFiles.forEach((file) => {
			const fontFamily = file.replace(".json", "");
			const collectionName = fontFamily.split("-").slice(0, -1).join("-");

			// Register the font with IconPool
			IconPool.registerFont({
				collectionName: collectionName,
				fontFamily: fontFamily,
				fontURI: "thirdparty/webfonts/",
				metadataURI: Component.FONT_DIR + file,
				lazy: false,
			});
		});
	}

	/**
	 * Gets the user instance.
	 * @returns The user instance
	 * @public
	 */
	public getUser(): User {
		return this.user;
	}

	/**
	 * Gets the menu instance.
	 * @returns The menu instance
	 * @public
	 */
	public getMenu(): Menu {
		return this.menu;
	}

	/**
	 * Gets the base hash.
	 * @returns The base hash
	 * @public
	 */
	public getBaseHash(): string {
		return this.baseHash;
	}

	/**
	 * Sets the base hash.
	 * @param hash - The base hash to set
	 * @public
	 */
	public setBaseHash(hash: string): void {
		this.baseHash = hash;
	}

	/**
	 * Sets the token in OData request headers.
	 * This method sets the Authorization header with the user's token for OData requests.
	 * @public
	 */
	public setTokenInODataRequestHeaders(): void {
		// Set request headers if needed
		const headers = {
			"Authorization": `Bearer ${this.user.getToken()}`,
		};
		const model = this.getModel();
		// if (model instanceof ODataModel) {
		// 	// ignore open requests
		// 	model.changeHttpHeaders(headers);
		// }
	}

	public fillTranslationCaches(): Promise<void[]> {
		const fillPromises = Object.values(this.translator).map((translator) => {
			if (!translator.isCached) {
				return translator.fillCache().then(() => {
					LogHandler.display(`Translation cache filled for ${translator.constructor.name}`);
				});
			} else {
				return Promise.resolve();
			}
		});
		return Promise.all(fillPromises);
	}
}
