package oving6.stock;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class SmartStockTest {

	private SmartStock stock;
	private double oldPrice;
	private double newPrice;

	// Used to check that listeners work
	private double oldPriceListener;
	private double newPriceListener;

	private void setPriceForListener(double oldPrice, double newPrice) {
		oldPriceListener = oldPrice;
		newPriceListener = newPrice;
	}

	private void setPriceCheckListener(String contextMessage, double newPrice,
			double expectedOldPrice, double expectedNewPrice) {
		// Update the price
		this.oldPrice = this.newPrice;
		this.newPrice = newPrice;
		stock.setPrice(newPrice);

		// Check that the listener has received the change
		assertEquals(expectedOldPrice, this.oldPriceListener,
				contextMessage + " -> Test old price for listener after updating price from "
						+ oldPrice + " to " + newPrice);
		assertEquals(expectedNewPrice, this.newPriceListener,
				contextMessage + " -> Test new price for listener after updating price from "
						+ oldPrice + " to " + newPrice);
	}

	@BeforeEach
	public void setUp() {
		stock = new SmartStock("APPL", 110.0);
	}

	@Test
	@DisplayName("Test constructor")
	public void testConstructor() {
		assertEquals("APPL", stock.getTicker(), "Test ticker");
		assertEquals(110.0, stock.getPrice(), "Test stock price");
	}

	@Test
	@DisplayName("Negative stock price throws error")
	public void testSetNegativePrice() {
		assertThrows(IllegalArgumentException.class, () -> {
			stock.setPrice(-20.0);
		}, "Test setting negative stock price");
	}

	@Test
	@DisplayName("Stock price equal to zero throws error")
	public void testSetZeroPrice() {
		assertThrows(IllegalArgumentException.class, () -> {
			stock.setPrice(0);
		}, "Test setting stock price equal to zero");
	}

	@Test
	@DisplayName("Add listener")
	public void testStockListener() {
		StockListener listener = (Stock stock, double oldPrice, double newPrice) -> this
				.setPriceForListener(oldPrice, newPrice);
		stock.addStockListener(listener);

		this.setPriceCheckListener("Listener on all", 118.0, 110.0, 118.0);
		assertEquals(118.0, stock.getPrice(), "Test stock price after updating price");

		this.setPriceCheckListener("Listener on all", 121.0, 118.0, 121.0);
		assertEquals(121.0, stock.getPrice(), "Test stock price after updating price twice");
	}

	@Test
	@DisplayName("Test listener on price interval")
	public void testIntervalListener() {
		StockListener listener = (Stock stock, double oldPrice, double newPrice) -> this
				.setPriceForListener(oldPrice, newPrice);
		stock.addStockListener(listener, 110.0, 120.0);

		// Price within the interval does not notify the listener
		this.setPriceCheckListener("Listener on price interval", 118.0, 0.0, 0.0);
		assertEquals(118.0, stock.getPrice(), "Test stock price after updating price");

		// Price outside the interval notifies the listener
		this.setPriceCheckListener("Listener on price interval", 121.0, 118.0, 121.0);
		assertEquals(121.0, stock.getPrice(),
				"Test stock price after updating price for the second time");

		// Price within the interval does not notify the listener (expected values remain unchanged)
		this.setPriceCheckListener("Listener on price interval", 115.0, 118.0, 121.0);
		assertEquals(115.0, stock.getPrice(),
				"Test stock price after updating price for the third time");
	}

	@Test
	@DisplayName("Test listener on difference")
	public void testDifferenceListener() {
		StockListener listener = (Stock stock, double oldPrice, double newPrice) -> this
				.setPriceForListener(oldPrice, newPrice);
		stock.addStockListener(listener, 10.0);

		// Price with a difference less than 10 does not notify the listener
		this.setPriceCheckListener("Listener on difference", 118.0, 0.0, 0.0);
		assertEquals(118.0, stock.getPrice());

		// Price with a difference greater than 10 notifies the listener
		this.setPriceCheckListener("Listener on difference", 121.0, 110.0, 121.0);
		assertEquals(121.0, stock.getPrice());

		// Price with a difference less than 10 does not notify the listener (expected values remain
		// unchanged)
		this.setPriceCheckListener("Listener on difference", 115.0, 110.0, 121.0);
		assertEquals(115.0, stock.getPrice());
	}
}
