/***********************************************************************
 * This file is part of iDempiere ERP Open Source                      *
 * http://www.idempiere.org                                            *
 *                                                                     *
 * Copyright (C) Contributors                                          *
 *                                                                     *
 * This program is free software; you can redistribute it and/or       *
 * modify it under the terms of the GNU General Public License         *
 * as published by the Free Software Foundation; either version 2      *
 * of the License, or (at your option) any later version.              *
 *                                                                     *
 * This program is distributed in the hope that it will be useful,     *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of      *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the        *
 * GNU General Public License for more details.                        *
 *                                                                     *
 * You should have received a copy of the GNU General Public License   *
 * along with this program; if not, write to the Free Software         *
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,          *
 * MA 02110-1301, USA.                                                 *
 *                                                                     *
 * Contributors:                                                       *
 * - hengsin                         								   *
 **********************************************************************/
package org.idempiere.test.model;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

import java.sql.Timestamp;
import java.time.format.DateTimeFormatter;
import java.util.Calendar;
import java.util.TimeZone;

import org.compiere.model.MClientInfo;
import org.compiere.model.MOrgInfo;
import org.compiere.model.MSchedule;
import org.compiere.model.MScheduler;
import org.compiere.util.CacheMgt;
import org.compiere.util.Env;
import org.compiere.util.Util;
import org.idempiere.test.AbstractTestCase;
import org.junit.jupiter.api.Test;

/**
 * @author hengsin
 *
 */
public class MSchedulerTest extends AbstractTestCase {

	/**
	 * 
	 */
	public MSchedulerTest() {
	}

	@Test
	public void testCronSchedulingPatternWithTimeZone() {
		MSchedule schedule = null;
		MClientInfo clientInfo = MClientInfo.getCopy(Env.getCtx(), getAD_Client_ID(), null);
		String currentTimeZone = clientInfo.getTimeZone();
		MOrgInfo orgInfo = MOrgInfo.getCopy(Env.getCtx(), getAD_Org_ID(), null);
		String currentTimeZoneOrg = orgInfo.getTimeZone();
		
		try {
			schedule = new MSchedule(Env.getCtx(), 0, null);
			schedule.setName("Every Day at 5 pm Test");
			schedule.setScheduleType(MSchedule.SCHEDULETYPE_CronSchedulingPattern);
			schedule.setIsSystemSchedule(false);
			schedule.setCronPattern("0 17 * * *");
			schedule.saveCrossTenantSafeEx();
			
			//get jvm timezone
			//this test assume jvm and db server is using the same default time zone
			TimeZone tz1 = TimeZone.getDefault();
			Calendar cal1 = Calendar.getInstance();
			cal1.setTimeZone(tz1);
			cal1.setTimeInMillis(System.currentTimeMillis());
			int hour = cal1.get(Calendar.HOUR_OF_DAY);
			if (hour >= 17) {
				cal1.add(Calendar.DAY_OF_MONTH, 1);
			}
			cal1.set(Calendar.HOUR_OF_DAY, 17);
			cal1.set(Calendar.MINUTE, 0);
			cal1.set(Calendar.SECOND, 0);
			cal1.set(Calendar.MILLISECOND, 0);
			
			//get timezone with +2 hour offset
			String[] ids = TimeZone.getAvailableIDs(tz1.getRawOffset()+(2*60*60*1000));
			TimeZone tz2 = TimeZone.getTimeZone(ids[0]);
			Calendar cal2 = Calendar.getInstance();
			cal2.setTimeZone(tz2);
			cal2.setTimeInMillis(System.currentTimeMillis());
			hour = cal2.get(Calendar.HOUR_OF_DAY);
			if (hour >= 17) {
				cal2.add(Calendar.DAY_OF_MONTH, 1);
			}
			cal2.set(Calendar.HOUR_OF_DAY, 17);
			cal2.set(Calendar.MINUTE, 0);
			cal2.set(Calendar.SECOND, 0);
			cal2.set(Calendar.MILLISECOND, 0);
			
			//get timezone with +3 hour offset
			ids = TimeZone.getAvailableIDs(tz1.getRawOffset()+(3*60*60*1000));
			TimeZone tz3 = TimeZone.getTimeZone(ids[0]);
			Calendar cal3 = Calendar.getInstance();
			cal3.setTimeZone(tz3);
			cal3.setTimeInMillis(System.currentTimeMillis());
			hour = cal3.get(Calendar.HOUR_OF_DAY);
			if (hour >= 17) {
				cal3.add(Calendar.DAY_OF_MONTH, 1);
			}
			cal3.set(Calendar.HOUR_OF_DAY, 17);
			cal3.set(Calendar.MINUTE, 0);
			cal3.set(Calendar.SECOND, 0);
			cal3.set(Calendar.MILLISECOND, 0);
			
			//formatter for comparison
			DateTimeFormatter formatter1 = DateTimeFormatter.ISO_ZONED_DATE_TIME;
			formatter1 = formatter1.withZone(tz1.toZoneId());
			DateTimeFormatter formatter2 = DateTimeFormatter.ISO_ZONED_DATE_TIME;
			formatter2 = formatter2.withZone(tz2.toZoneId());
			DateTimeFormatter formatter3 = DateTimeFormatter.ISO_ZONED_DATE_TIME;
			formatter3 = formatter3.withZone(tz3.toZoneId());
			
			//test with default time zone
			if (!Util.isEmpty(currentTimeZone, true)) {
				clientInfo.setTimeZone(null);
				clientInfo.saveEx();
				CacheMgt.get().reset();
			}
			
			//test with default time zone
			if (!Util.isEmpty(currentTimeZoneOrg, true)) {
				orgInfo.setTimeZone(null);
				orgInfo.saveEx();
				CacheMgt.get().reset();
			}
			
			MScheduler scheduler1 = new MScheduler(Env.getCtx(), 0, getTrxName());
			scheduler1.setAD_Process_ID(121); //Open Orders Process Id
			scheduler1.setAD_Schedule_ID(schedule.get_ID());
			scheduler1.setName("Cron Scheduler Test 1");
			scheduler1.setSupervisor_ID(100);
			scheduler1.saveEx();
			
			Timestamp ts1 = scheduler1.getDateNextRun();
			assertEquals(formatter1.format(cal1.getTime().toInstant()), formatter1.format(ts1.toInstant()), "Un-expected date next run");
			assertFalse(cal2.getTimeInMillis() == ts1.getTime(), "Un-expected date next run");
			
			//test with default + 2hour time zone (defined in tenant)
			clientInfo.setTimeZone(tz2.toZoneId().getId());
			clientInfo.saveEx();
			CacheMgt.get().reset();
			
			MScheduler scheduler2 = new MScheduler(Env.getCtx(), 0, getTrxName());
			scheduler2.setAD_Process_ID(121);
			scheduler2.setAD_Schedule_ID(schedule.get_ID());
			scheduler2.setName("Cron Scheduler Test 2");
			scheduler2.setSupervisor_ID(100);
			scheduler2.saveEx();
			
			Timestamp ts2 = scheduler2.getDateNextRun();
			assertEquals(formatter2.format(cal2.getTime().toInstant()), formatter2.format(ts2.toInstant()), "Un-expected date next run");
			assertFalse(cal1.getTimeInMillis() == ts2.getTime(), "Un-expected date next run");
			
			//test with default + 3hour time zone (defined in org)
			orgInfo.setTimeZone(tz3.toZoneId().getId());
			orgInfo.saveEx();
			CacheMgt.get().reset();
			
			MScheduler scheduler3 = new MScheduler(Env.getCtx(), 0, getTrxName());
			scheduler3.setAD_Process_ID(121);
			scheduler3.setAD_Schedule_ID(schedule.get_ID());
			scheduler3.setName("Cron Scheduler Test 3");
			scheduler3.setSupervisor_ID(100);
			scheduler3.saveEx();
			
			Timestamp ts3 = scheduler3.getDateNextRun();
			assertEquals(formatter3.format(cal3.getTime().toInstant()), formatter3.format(ts3.toInstant()), "Un-expected date next run");
			assertFalse(cal1.getTimeInMillis() == ts3.getTime(), "Un-expected date next run");
			
		} finally {
			rollback();
			if (schedule != null && schedule.get_ID() > 0)
				schedule.deleteEx(true);
			clientInfo.setTimeZone(currentTimeZone);
			if (clientInfo.is_Changed()) {
				clientInfo.saveEx();
				CacheMgt.get().reset();
			}
			
			orgInfo.setTimeZone(currentTimeZoneOrg);
			if (orgInfo.is_Changed()) {
				orgInfo.saveEx();
				CacheMgt.get().reset();
			}
		}
	}
}
