import * as HandleBars from 'handlebars';
const { parseBoolean } = require('../condition-executor/condition-handler');
const momentTimezone = require('moment-timezone');
const lodash = require('lodash');

function getEwayBillValues(ewayBill) {
	const invoiceDate = lodash.get(ewayBill, 'invoice_date') || lodash.get(ewayBill, 'invoiceDate') || '';
	const invoiceNumber = lodash.get(ewayBill, 'invoice_number') || lodash.get(ewayBill, 'invoiceNumber') || '';
	const invoiceValue = lodash.get(ewayBill, 'invoiceValue') || lodash.get(ewayBill, 'invoice_amount') || lodash.get(ewayBill, 'invoice_value') || '';
	const ewbNumber = lodash.get(ewayBill, 'ewb_number') || lodash.get(ewayBill, 'ewbNumber') || '';
	const expiryDate = lodash.get(ewayBill, 'expiry_date') || lodash.get(ewayBill, 'validTill') || '';
	const ewbCreationDate = lodash.get(ewayBill, 'creationTime') || lodash.get(ewayBill, 'creation_time') || '';

	return {
		invoiceDate,
		invoiceNumber,
		invoiceValue,
		ewbNumber,
		expiryDate,
		ewbCreationDate,
	};
}

// registering all the helpers for Handlerbar
(async function() {
	// ifEquals helper for comparing two strings
	HandleBars.registerHelper('ifEquals', (a, b) => {
		return (a ? a.toUpperCase() : '') === b.toUpperCase();
	});

	// exists helper for checking value is some truthy value and not falsy
	HandleBars.registerHelper('exists', (val) => {
		return !(val === undefined || val === null);
	});

	// toUpperCase helper for converting string to uppper case
	HandleBars.registerHelper('toUpperCase', (val) => {
		return val.toUpperCase();
	});

	// toLowerCase helper for converting string to lower case
	HandleBars.registerHelper('toLowerCase', (val) => {
		return val.toLowerCase();
	});

	// "not" helper use with if
	HandleBars.registerHelper('not', function(val) {
		return !val;
	});

	HandleBars.registerHelper('lookupByIndex', function(array, index) {
		return array[index];
	});

	HandleBars.registerHelper('multiply', function(value, multiplier) {
		if (!value || !multiplier) {
			return 0;
		}
		return (Number(value) * Number(multiplier)) || 0;
	});

	HandleBars.registerHelper('dateConvertor', function(value, fromFormat, fromTimezone, toFormat, toTimezone) {
		let moment;

		if (value) {
			moment = momentTimezone(value, fromFormat, fromTimezone);
		} else {
			moment = momentTimezone();
		}

		moment = moment.tz(toTimezone);

		if (toFormat === 'timestamp') {
			return moment.valueOf();
		}
		return moment.format(toFormat);
	});

	HandleBars.registerHelper('getBasicAuth', function(username, password) {
		try {
			const buff = new Buffer(username + ':' + password);
			const base64data = buff.toString('base64');
			return base64data;
		} catch (err) {
			return null;
		}
	});

	HandleBars.registerHelper('concat', function() {
		return Array.prototype.slice.call(arguments, 0, -1).join('');
	});

	HandleBars.registerHelper('toNumber', function(value) {
		return Number(value);
	});

	HandleBars.registerHelper('toString', function(value) {
		return String(value);
	});
	
	HandleBars.registerHelper('toBoolean', function(value) {
		return parseBoolean(value);
	});

	HandleBars.registerHelper('getEwayBillValues', function(ewayBill) {
		return getEwayBillValues(ewayBill);
	});

	HandleBars.registerHelper('getInvoiceValues', function(ewayBillList, consignmentExtraDetails) {
			let invoiceNumber = '';
			let invoiceDate = '';
			let ewbNumber = '';
			let invoiceAmount: any = '';
			let expiryDate = '';
			let ewbCreationDate = '';
	
			if (Array.isArray(ewayBillList) && ewayBillList.length) {
				const ewayBillValues = getEwayBillValues(ewayBillList[0]);
				invoiceNumber = ewayBillValues.invoiceNumber;
				invoiceDate = ewayBillValues.invoiceDate;
				ewbNumber = ewayBillValues.ewbNumber;
				invoiceAmount = ewayBillValues.invoiceValue;
				expiryDate = ewayBillValues.expiryDate;
				ewbCreationDate = ewayBillValues.ewbCreationDate;
			}
			if (consignmentExtraDetails.eway_bill) {
				const ewayBillValues = getEwayBillValues(consignmentExtraDetails.eway_bill);
				if (!invoiceNumber) invoiceNumber = ewayBillValues.invoiceNumber;
				if (!invoiceDate) invoiceDate = ewayBillValues.invoiceDate;
				if (!ewbNumber) ewbNumber = ewayBillValues.ewbNumber;
				if (!expiryDate) expiryDate = ewayBillValues.expiryDate;
				if (!ewbCreationDate) ewbCreationDate = ewayBillValues.ewbCreationDate;
			}
			if (!invoiceNumber) {
				invoiceNumber = lodash.get(consignmentExtraDetails, 'invoice_number', '');
			}
			if (!invoiceDate) {
				invoiceDate = lodash.get(consignmentExtraDetails, 'invoice_date', '');
			}
			if (!invoiceAmount) {
				invoiceAmount = lodash.get(consignmentExtraDetails, 'invoice_amount', '');
			}
	
			return {
				invoiceNumber,
				invoiceDate,
				ewbNumber,
				invoiceAmount,
				expiryDate,
				ewbCreationDate,
			};
	});


	HandleBars.registerHelper('sumProperty', function(piecesDetail, property) {
		if (!piecesDetail || !Array.isArray(piecesDetail)) return 0;
		
		return piecesDetail.reduce((sum, item) => {
		  if (!item) return sum;
		  
		  const value = item[property];
		  if (value === undefined || value === null) return sum;
		  
		  const numValue = parseFloat(value);
		  return sum + (isNaN(numValue) ? 0 : numValue);
		}, 0);
	  });	

	HandleBars.registerHelper('getRegexMatch', function(str, pattern, groupIndex=0, flags='') {

		if (typeof str !== 'string') { 
			return ''; 
	  	}
	  
	  	if (str === '') {
			return '';
	  	}
	  
		let index: number;
		if (typeof groupIndex === 'string') {
			index = parseInt(groupIndex, 10);
		} else if (typeof groupIndex === 'number') {
			index = groupIndex;
		} else {
			return '';
		}

		if (isNaN(index)) {
			return '';
		}
	  
	  	try {
			const patternStr = String(pattern);
			const validFlags = flags.replace(/[^gimsuyd]/g, '');
		
			const regex = new RegExp(patternStr, validFlags);
			const match = str.match(regex);
		
			if (match && match[index] !== undefined) {
		  		return match[index];
			} else {
		  		return '';
			}
	  	} catch (error) {
			return '';
	  	}
	  });

	HandleBars.registerHelper('arrayProperty', function (array, options) {
		const { 
			operation, 
			property, 
			value, 
			order = 'asc',
			dataType,
			checkType = 'any'
		  } = options.hash;
		  
		  if (!Array.isArray(array)) {
			return operation === 'exists' ? false : [];
		  }
		  
		  function getNestedValue(obj, path) {
			return path.split('.').reduce((acc, key) => acc?.[key], obj);
		  }
		  
		  function isValidValue(val, type) {
			if (val == null) return false;
			
			switch ((type || '').toLowerCase()) {
			  case 'string':
				return typeof val === 'string' && val.trim() !== '';
			  case 'number':
				return typeof val === 'number' && !isNaN(val);
			  case 'boolean':
				return typeof val === 'boolean';
			  case 'array':
				return Array.isArray(val) && val.length > 0;
			  case 'object':
				return typeof val === 'object' && !Array.isArray(val) && Object.keys(val).length > 0;
			  case 'any':
			  default:
				if (Array.isArray(val)) return val.length > 0;
				if (typeof val === 'object') return Object.keys(val).length > 0;
				return Boolean(val);
			}
		  }
		  
		  switch (operation) {
			case 'exists':
			  if (checkType === 'every') {
				return array.every(item => isValidValue(getNestedValue(item, property), dataType));
			  } else {
				return array.some(item => isValidValue(getNestedValue(item, property), dataType));
			  }
			case 'map':
			  return array.map(item => getNestedValue(item, property));
			case 'filter':
			  return array.filter(item => getNestedValue(item, property) == value);
			case 'find':
			  return array.find(item => getNestedValue(item, property) == value);
			case 'sort':
			  return array.slice().sort((a, b) => {
				  const aVal = getNestedValue(a, property);
				  const bVal = getNestedValue(b, property);
				  if (aVal == null && bVal == null) return 0;
				  if (aVal == null) return 1;  
				  if (bVal == null) return -1;
				  
				  if (order === 'desc') {
					return aVal < bVal ? 1 : aVal > bVal ? -1 : 0;
				  }
				  return aVal > bVal ? 1 : aVal < bVal ? -1 : 0;
				});
			case 'unique':
			  const seen = new Set();
			  return array.filter(item => {
				const val = getNestedValue(item, property);
				if (seen.has(val)) return false;
				seen.add(val);
				return true;
			  });
			case 'sum':
			  return array.reduce((acc, item) => {
				const val = getNestedValue(item, property);
				const num = parseFloat(val);
				return acc + (isNaN(num) ? 0 : num);
			  }, 0);
			default:
			  return array;
		  }
	  });	  

})();

export const getTemplateData = (template: string, data: any) => {
	const compiledTemplate = HandleBars.compile(template);
	return compiledTemplate(data);
};

export const cleanHandleBarTemplate = (dataToClean, defaultBody) => {
	dataToClean = dataToClean.replace('\n', '');
	dataToClean = dataToClean.replace('\t', '');
	return dataToClean || defaultBody;
};
