package org.ff4j.test.audit;

/*-
 * #%L
 * ff4j-test
 * %%
 * Copyright (C) 2013 - 2023 FF4J
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */

import static org.ff4j.audit.EventConstants.ACTION_CHECK_OFF;
import static org.ff4j.audit.EventConstants.ACTION_CHECK_OK;
import static org.ff4j.audit.EventConstants.ACTION_CREATE;
import static org.ff4j.audit.EventConstants.SOURCE_JAVA;
import static org.ff4j.audit.EventConstants.SOURCE_WEB;
import static org.ff4j.audit.EventConstants.SOURCE_WEBAPI;
import static org.ff4j.audit.EventConstants.TARGET_FEATURE;

import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.ff4j.FF4j;
import org.ff4j.audit.Event;
import org.ff4j.audit.EventConstants;
import org.ff4j.audit.EventPublisher;
import org.ff4j.audit.EventQueryDefinition;
import org.ff4j.audit.EventSeries;
import org.ff4j.audit.MutableHitCount;
import org.ff4j.audit.chart.BarChart;
import org.ff4j.audit.chart.TimeSeriesChart;
import org.ff4j.audit.repository.EventRepository;
import org.ff4j.core.Feature;
import org.ff4j.property.store.InMemoryPropertyStore;
import org.ff4j.store.InMemoryFeatureStore;
import org.ff4j.utils.Util;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

/**
 * Event Repository test support.
 *
 * @author Cedrick LUNVEN (@clunven)
 */
public abstract class EventRepositoryTestSupport {

	/** Initialize */
	protected FF4j ff4j = null;

	/** Feature List. */
	protected ArrayList<Feature> features;

	/** Target {@link EventRepository}. */
	protected EventRepository repo;

	/** Target publisher. */
	protected EventPublisher publisher;

	/** {@inheritDoc} */
	@Before
	public void setUp() throws Exception {
		ff4j = new FF4j();
		ff4j.setFeatureStore(new InMemoryFeatureStore("test-ff4j-features.xml"));
		ff4j.setPropertiesStore(new InMemoryPropertyStore("test-ff4j-features.xml"));
		ff4j.setEventRepository(initRepository());
		repo = ff4j.getEventRepository();
	}

	/**
	 * Event generation.
	 * 
	 * @param uid
	 *            target unique identifier
	 * @return unique event
	 */
	protected Event generateFeatureUsageEvent(String uid) {
		return new Event(SOURCE_JAVA, TARGET_FEATURE, uid, ACTION_CHECK_OK);
	}

	/**
	 * Event generation.
	 *
	 * @param uid
	 *            target unique identifier
	 * @param timestamp
	 *            current event time
	 * @return
	 */
	protected Event generateFeatureUsageEvent(String uid, long timestamp) {
		Event event = generateFeatureUsageEvent(uid);
		event.setTimestamp(timestamp);
		return event;
	}

	/**
	 * Generate random event.
	 *
	 * @param uid
	 *            event uid
	 * @param from
	 *            time slot in
	 * @param to
	 *            time slot to
	 * @return target event
	 */
	protected Event generateRandomFeatureUsageEvent(String uid, long from, long to) {
		return generateFeatureUsageEvent(uid, from + (long) (Math.random() * (to - from)));
	}

	/**
	 * Generate random event.
	 *
	 * @param uid
	 *            event uid
	 * @param from
	 *            time slot in
	 * @param to
	 *            time slot to
	 * @return target event
	 */
	protected Event generateRandomFeatureUsageEvent(long from, long to) {
		return generateRandomFeatureUsageEvent(Util.getRandomElement(features).getUid(), from, to);
	}

	/**
	 * Generate random events for.
	 */
	protected void populateRepository(long from, long to, int totalEvent) throws InterruptedException {
		for (int i = 0; i < totalEvent; i++) {
			repo.saveEvent(generateRandomFeatureUsageEvent(from, to));
		}
	}

	/**
	 * Any store test will declare its store through this callback.
	 * 
	 * @return working feature store
	 * @throws Exception
	 *             error during building feature store
	 */
	protected abstract EventRepository initRepository();

	/** TDD. */
	@Test
	public void testSaveEventUnit() throws InterruptedException {
		long start = System.currentTimeMillis();
		// Given
		EventQueryDefinition eqd = new EventQueryDefinition(start, System.currentTimeMillis());
		Assert.assertEquals(0, repo.getFeatureUsageTotalHitCount(eqd));
		// When
		repo.saveEvent(generateFeatureUsageEvent("f1"));
		// Wait for the event to be effectively store
		Thread.sleep(100);
		// Then
		EventQueryDefinition eqd2 = new EventQueryDefinition(start - 20, System.currentTimeMillis());
		Assert.assertEquals(1, repo.getFeatureUsageTotalHitCount(eqd2));
	}

	/** TDD. */
	@Test(expected = IllegalArgumentException.class)
	public void testSaveEventNull() {
		Assert.assertFalse(repo.saveEvent(null));
	}

	/** TDD. */
	@Test
	public void testSaveAuditTrail() throws InterruptedException {
		long start = System.currentTimeMillis();
		// Given
		Event evt1 = new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", EventConstants.ACTION_CREATE);
		// When
		repo.saveEvent(evt1);
		// Wait for the event to be effectively store
		Thread.sleep(200);
		EventQueryDefinition eqd2 = new EventQueryDefinition(start - 200, System.currentTimeMillis());
		Assert.assertEquals(1, repo.getAuditTrail(eqd2).size());
	}

	/** TDD. */
	@Test
	public void testFeatureUsageBarCharts() throws InterruptedException {
		long start = System.currentTimeMillis();
		// Given empty event repository
		// When
		repo.saveEvent(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", EventConstants.ACTION_CREATE));
		for (int i = 0; i < 8; i++) {
			Thread.sleep(100);
			repo.saveEvent(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CHECK_OK));
			repo.saveEvent(new Event(SOURCE_WEB, TARGET_FEATURE, "f2", ACTION_CHECK_OK));
		}
		// Then : Assert bar chart (2 bars with 8 and 8)
		EventQueryDefinition testQuery = new EventQueryDefinition(start - 10, System.currentTimeMillis() + 10);
		BarChart bChart = repo.getFeatureUsageBarChart(testQuery);

		Assert.assertEquals(2, bChart.getChartBars().size());
		Assert.assertEquals(new Integer(8), bChart.getChartBars().get(0).getValue());
		Assert.assertEquals(new Integer(8), bChart.getChartBars().get(1).getValue());
		Assert.assertNotNull(bChart.getChartBars().get(0).getColor());
		Assert.assertNotNull(bChart.getChartBars().get(1).getColor());
	}

	/** TDD. */
	@Test
	public void testFeatureUsageHitCount() throws InterruptedException {
		long start = System.currentTimeMillis();
		// Given empty event repository
		// When
		repo.saveEvent(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CREATE));
		for (int i = 0; i < 8; i++) {
			Thread.sleep(100);
			repo.saveEvent(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CHECK_OK));
			repo.saveEvent(new Event(SOURCE_WEB, TARGET_FEATURE, "f2", ACTION_CHECK_OK));
		}
		Thread.sleep(100);

		// Then
		EventQueryDefinition testQuery = new EventQueryDefinition(start, System.currentTimeMillis());
		// Assert Pie Chart (2 sectors with 8 and 8)
		Map<String, MutableHitCount> mapOfHit = repo.getFeatureUsageHitCount(testQuery);
		Assert.assertEquals(2, mapOfHit.size());
		Assert.assertTrue(mapOfHit.containsKey("f1"));
		Assert.assertTrue(mapOfHit.containsKey("f2"));
		Assert.assertEquals(8, mapOfHit.get("f1").get());
	}

	@Test
	public void testSearchFeatureUsageEvents() throws InterruptedException {
		long start = System.currentTimeMillis();
		repo.saveEvent(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CREATE));
		for (int i = 0; i < 8; i++) {
			Thread.sleep(100);
			repo.saveEvent(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CHECK_OK));
			repo.saveEvent(new Event(SOURCE_WEB, TARGET_FEATURE, "f2", ACTION_CHECK_OK));
		}
		Thread.sleep(100);

		// Then
		EventQueryDefinition testQuery = new EventQueryDefinition(start - 20, System.currentTimeMillis());
		EventSeries es = repo.searchFeatureUsageEvents(testQuery);
		Assert.assertEquals(16, es.size());

		// Then

	}

	@Test
	public void testGetFeatureUsageHistory() throws InterruptedException {
		long start = System.currentTimeMillis();
		repo.saveEvent(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CREATE));
		for (int i = 0; i < 8; i++) {
			Thread.sleep(100);
			repo.saveEvent(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CHECK_OK));
			repo.saveEvent(new Event(SOURCE_WEB, TARGET_FEATURE, "f2", ACTION_CHECK_OK));
		}
		Thread.sleep(100);

		// Then
		EventQueryDefinition testQuery = new EventQueryDefinition(start - 20, System.currentTimeMillis());
		TimeSeriesChart tsc = repo.getFeatureUsageHistory(testQuery, TimeUnit.HOURS);
		Assert.assertEquals(1, tsc.getTimeSlots().size());
	}

	/** TDD. */
	@Test
	public void testSourceHitCount() throws InterruptedException {
		long start = System.currentTimeMillis();
		// When
		for (int i = 0; i < 8; i++) {
			Thread.sleep(100);
			repo.saveEvent(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CHECK_OK));
			repo.saveEvent(new Event(SOURCE_WEB, TARGET_FEATURE, "f2", ACTION_CHECK_OK));
		}
		Thread.sleep(200);
		repo.saveEvent(new Event(SOURCE_WEBAPI, TARGET_FEATURE, "f1", ACTION_CHECK_OK));
		Thread.sleep(200);

		// Then
		EventQueryDefinition testQuery = new EventQueryDefinition(start - 20, System.currentTimeMillis());
		Map<String, MutableHitCount> mapOfHit = repo.getSourceHitCount(testQuery);
		Assert.assertEquals(3, mapOfHit.size());
		Assert.assertTrue(mapOfHit.containsKey(SOURCE_JAVA));
		Assert.assertTrue(mapOfHit.containsKey(SOURCE_WEB));
		Assert.assertEquals(1, mapOfHit.get(SOURCE_WEBAPI).get());
	}

	/** TDD. */
	@Test
	public void testUserHitCount() throws InterruptedException {
		long start = System.currentTimeMillis();
		// When
		for (int i = 0; i < 8; i++) {
			Event e1 = new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CHECK_OK);
			e1.setUser("JOHN");
			repo.saveEvent(e1);
			Thread.sleep(100);

			Event e2 = new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CHECK_OK);
			e2.setUser("BOB");
			repo.saveEvent(e2);
			Thread.sleep(100);
		}
		Thread.sleep(200);

		// Then
		EventQueryDefinition testQuery = new EventQueryDefinition(start - 20, System.currentTimeMillis());
		Map<String, MutableHitCount> mapOfHit = repo.getUserHitCount(testQuery);
		Assert.assertEquals(2, mapOfHit.size());
		Assert.assertTrue(mapOfHit.containsKey("JOHN"));
		Assert.assertTrue(mapOfHit.containsKey("BOB"));
		Assert.assertEquals(8, mapOfHit.get("BOB").get());
	}

	/** TDD. */
	@Test
	public void testHostHitCount() throws InterruptedException {
		long start = System.currentTimeMillis();
		// When
		for (int i = 0; i < 8; i++) {
			Thread.sleep(100);
			repo.saveEvent(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CHECK_OK));
		}
		Thread.sleep(200);

		// Then
		EventQueryDefinition testQuery = new EventQueryDefinition(start, System.currentTimeMillis());
		Map<String, MutableHitCount> mapOfHit = repo.getHostHitCount(testQuery);
		Assert.assertEquals(1, mapOfHit.size());
		Assert.assertEquals(1, mapOfHit.values().size());
	}

	/** TDD. */
	@Test
	public void testSaveCheckOff() throws InterruptedException {
		long start = System.currentTimeMillis();
		// Given
		Event evt1 = new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CHECK_OFF);
		// When
		Assert.assertTrue(repo.saveEvent(evt1));
		Thread.sleep(100);
		// Then
		Assert.assertEquals(0,
				repo.getFeatureUsageTotalHitCount(new EventQueryDefinition(start, System.currentTimeMillis())));
		Assert.assertEquals(0, repo.getAuditTrail(new EventQueryDefinition(start, System.currentTimeMillis())).size());
	}

	/** TDD. */
	@Test
	public void testLimitEventSeries() throws InterruptedException {
		EventSeries es = new EventSeries(5);
		for (int i = 0; i < 10; i++) {
			Thread.sleep(10);
			es.add(new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CREATE));
		}
		Assert.assertEquals(5, es.size());
	}

	/** TDD. */
	@Test
	public void testGetEventByUID() throws InterruptedException {
		// Given
		String dummyId = "00000000-1111-2222-3333-444444444444";
		Event evt1 = new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CHECK_OFF);
		evt1.setUuid(dummyId);
		// When
		repo.saveEvent(evt1);
		// Let the store to be updated
		Thread.sleep(100);
		// Then
		Event evt = repo.getEventByUUID(dummyId, System.currentTimeMillis());
		Assert.assertNotNull(evt);
	}

	/** TDD. */
	@Test
	public void testPurgeEvents() throws InterruptedException {
		long topStart = System.currentTimeMillis();
		
		// When create audit event
		Event evtAudit = new Event(SOURCE_JAVA, TARGET_FEATURE, "f1", ACTION_CREATE);
		evtAudit.setUuid("00000000-5555-2222-3333-444444444444");
		repo.saveEvent(evtAudit);
		Thread.sleep(100);
		// Then event is present
		Assert.assertNotNull(repo.getEventByUUID(evtAudit.getUuid(), System.currentTimeMillis()));
		
        // When purging audit trail
		EventQueryDefinition testQuery = new EventQueryDefinition(topStart - 100, System.currentTimeMillis());
		repo.purgeAuditTrail(testQuery);
		Thread.sleep(300);
		// Then audi trail is purged
		Assert.assertNull(repo.getEventByUUID(evtAudit.getUuid(), System.currentTimeMillis()));

		// ----------------------
		
		// When create feature usage
        Event evtFeatureUsage = new Event(SOURCE_JAVA, TARGET_FEATURE, "f2", ACTION_CHECK_OK);
        evtFeatureUsage.setUuid("00000000-6666-2222-3333-444444444444");
        repo.saveEvent(evtFeatureUsage);
        Thread.sleep(100);
        // Then event if present
        Assert.assertNotNull(repo.getEventByUUID(evtFeatureUsage.getUuid(), System.currentTimeMillis()));
        
		// When
		EventQueryDefinition testQuery2 = new EventQueryDefinition(topStart - 100, System.currentTimeMillis());
        repo.purgeFeatureUsage(testQuery2);
		Thread.sleep(300);
		// Then event 2 is deleted
		Assert.assertNull(repo.getEventByUUID(evtFeatureUsage.getUuid(), System.currentTimeMillis()));
	}

}
