package com.kpl.CourierMgmt.CourierManager;

import com.kpl.CourierMgmt.GoogleMapClient.GoogleRoute;
import com.kpl.CourierMgmt.GoogleMapClient.IGoogleMapApi;
import static org.junit.jupiter.api.Assertions.*;

import java.time.Duration;
import java.util.stream.Stream;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentMatchers;

import static org.mockito.Mockito.*;

public class CourierBookingTests {
	IFareCalculator fareCalculator;
    IGoogleMapApi googleMapper;
    CourierBooking booking;

    @BeforeEach
    public void Setup()
    {
        fareCalculator = new BasicFareCalculator();
        googleMapper = new FakeGoogleMapper();
    }

    @Test
    public void PremiumFareUsingHandCraftedMockObjects()
    {
        // ARRANGE ...

        MockFareCalculator fareCalculator = new MockFareCalculator(100, 2.4, 5.0);
        MockPremiumCalculator premiumCalculator = new MockPremiumCalculator(33, HazardType.Corrosive, 12000, 2.4, 5.0);

        //IGoogleMapApi googleMapApi = new FakeGoogleMapper();
        GoogleRoute r = new GoogleRoute();
        r.Destination = "Home";
        r.Origin = "Work";
        r.Distance = 2.4;
        r.Duration = Duration.ofMinutes(20);
        MockGoogleMapApi googleMapApi = new MockGoogleMapApi(r, "Work", "Home", 1);
        booking = new CourierBooking
            (5.0, googleMapApi.GetRoute("Work", "Home"), fareCalculator, premiumCalculator);
        booking.PackageHazardType = HazardType.Corrosive;
        booking.PackageValue = 12000;

        // ACT ...

        int fare = booking.GetFare();

        // ASSERT ...

        assertEquals(133, fare);
        assertTrue(fareCalculator.ExpectationsHaveBeenMet()); 
        assertTrue(premiumCalculator.ExpectationsHaveBeenMet());
        assertTrue(googleMapApi.ExpectationsHaveBeenMet());
    }
    
    @Test
    public void PremiumFareUsingMockito()
    {
        // ARRANGE ...

        // MockFareCalculator fareCalculator = new MockFareCalculator(100, 2.4, 5.0);
    	IFareCalculator fareCalculator = mock(IFareCalculator.class);
        // MockPremiumCalculator premiumCalculator = new MockPremiumCalculator(33, HazardType.Corrosive, 12000, 2.4, 5.0);
        IPremiumCalculator premiumCalculator = mock(IPremiumCalculator.class);
    	
        //IGoogleMapApi googleMapApi = new FakeGoogleMapper();
        GoogleRoute r = new GoogleRoute();
        r.Destination = "Home";
        r.Origin = "Work";
        r.Distance = 2.4;
        r.Duration = Duration.ofMinutes(20);
        // MockGoogleMapApi googleMapApi = new MockGoogleMapApi(r, "Work", "Home", 1);
        IGoogleMapApi googleMapApi = mock(IGoogleMapApi.class);
        when(googleMapApi.GetRoute("Work",  "Home")).thenReturn(r); // GOT HERE
        when(fareCalculator.CalculateFare(2.4,  5.0)).thenReturn(100);
        when(premiumCalculator.CalculatePremium(HazardType.Corrosive,  12000,  2.4,  5.0)).thenReturn(33);
        booking = new CourierBooking
            (5.0, googleMapApi.GetRoute("Work", "Home"), fareCalculator, premiumCalculator);
        booking.PackageHazardType = HazardType.Corrosive;
        booking.PackageValue = 12000;

        // ACT ...

        int fare = booking.GetFare();

        // ASSERT ...

        assertEquals(133, fare);
        
        verify(googleMapApi, times(1)).GetRoute(ArgumentMatchers.eq("Work"), ArgumentMatchers.eq("Home"));
        verify(fareCalculator, times(1)).CalculateFare(ArgumentMatchers.eq(2.4), ArgumentMatchers.eq(5.0));
        verify(premiumCalculator, times(1)).CalculatePremium(ArgumentMatchers.eq(HazardType.Corrosive),  
        		ArgumentMatchers.eq(12000),  ArgumentMatchers.eq(2.4),  ArgumentMatchers.eq(5.0));
    }
    
    @Test
    public void PremiumFareIllegalPackage()
    {
        // ARRANGE ...

        // MockFareCalculator fareCalculator = new MockFareCalculator(100, 2.4, 5.0);
    	IFareCalculator fareCalculator = mock(IFareCalculator.class);
        // MockPremiumCalculator premiumCalculator = new MockPremiumCalculator(33, HazardType.Corrosive, 12000, 2.4, 5.0);
        IPremiumCalculator premiumCalculator = mock(IPremiumCalculator.class);
    	
        //IGoogleMapApi googleMapApi = new FakeGoogleMapper();
        GoogleRoute r = new GoogleRoute();
        r.Destination = "Home";
        r.Origin = "Work";
        r.Distance = 2.4;
        r.Duration = Duration.ofMinutes(20);
        // MockGoogleMapApi googleMapApi = new MockGoogleMapApi(r, "Work", "Home", 1);
        IGoogleMapApi googleMapApi = mock(IGoogleMapApi.class);
        when(googleMapApi.GetRoute("Work",  "Home")).thenReturn(r); // GOT HERE
        when(fareCalculator.CalculateFare(2.4,  5.0))
            .thenThrow(new IllegalArgumentException("Fare calculation failed"));
        when(premiumCalculator.CalculatePremium(HazardType.Corrosive,  12000,  2.4,  5.0))
            .thenThrow(new IllegalArgumentException("Premium fare calculation failed"));
        booking = new CourierBooking
            (5.0, googleMapApi.GetRoute("Work", "Home"), fareCalculator, premiumCalculator);
        booking.PackageHazardType = HazardType.Corrosive;
        booking.PackageValue = 12000;

        // ACT ...

        IllegalArgumentException x = assertThrows(IllegalArgumentException.class, () -> {
        	int fare = booking.GetFare();
        });

        // ASSERT ...

        verify(googleMapApi, times(1)).GetRoute(ArgumentMatchers.eq("Work"), ArgumentMatchers.eq("Home"));
        verify(fareCalculator, times(1)).CalculateFare(anyDouble(), anyDouble());
        verify(premiumCalculator, never()).CalculatePremium(any(HazardType.class),  
        		anyInt(),  anyDouble(),  anyDouble());
    }
    
    @Test
    public void FareForLargeDistanceAndWeight()
    {
        // ARRANGE ...

        IGoogleMapApi googleMapApi = new FakeGoogleMapper();
        booking = new CourierBooking(10.0, googleMapApi.GetRoute("Bridge of Sighs", "Venice Station"), fareCalculator);

        // ACT ...

        int fare = booking.GetFare();

        // ASSERT ...

        assertEquals(3000, fare);
    }

    @Test
    public void FareForSmallDistanceAndWeight()
    {
        // ARRANGE ...

        IGoogleMapApi googleMapApi = new FakeGoogleMapper();
        booking = new CourierBooking(1.0, googleMapApi.GetRoute("Bridge of Sighs", "Venice Station"), fareCalculator);

        // ACT ...

        int fare = booking.GetFare();

        // ASSERT ...

        assertEquals(1500, fare);
    }

    @Test
    public void FareWithInvalidWeight()
    {
        // ARRANGE ...
        booking = new CourierBooking(-1.0, new FakeGoogleRoute("Bridge of Sighs", "Venice Station", 1.0, 100), fareCalculator);

        // ACT ...
		IllegalArgumentException x = assertThrows(IllegalArgumentException.class, () -> {
		    int fare = booking.GetFare();
		});
		
        // ASSERT ...
        assertNotNull(x);
    }
    
    @ParameterizedTest
	@MethodSource("routeFareProvider")
    public void AssortedWeightsAndDistances(String pickup, String drop, double weight, int expectedPrice)
    {
    	// ARRANGE ...

        IGoogleMapApi googleMapApi = new FakeGoogleMapper();
        booking = new CourierBooking(weight, googleMapApi.GetRoute(pickup, drop), fareCalculator);

        // ACT ...

        int fare = booking.GetFare();

        // ASSERT ...

        assertEquals(expectedPrice, fare);
    }
	
	static Stream<Arguments> routeFareProvider() {
		return Stream.of(
			Arguments.of("Bridge of Sighs", "Venice Station", 5.0, 1500), 
			Arguments.of("Gallerie del'Accademia", "Rialto Bridge", 7.0, 1500),
			Arguments.of("Rialto Bridge", "Venice Station", 8.0, 1600) 
		);
	}


/*
    [TestMethod]
    public void MoqDemo()
    {
        // ARRANGE ...

        var myMock = new Mock<ISomeService>(MockBehavior.Strict);

        double firstVal;
        string firstName;
        myMock.Setup(iss => iss
            .DoThis("Joe", 4.44))
            .Returns(21)
            .Callback<string, double>((n, v) => { firstName = n; firstVal = v; });

        myMock.Setup(iss => iss.DoThis("Joe", 4.44)).Returns(21);
        myMock.Setup(iss => iss.DoThis("Fred", It.IsAny<double>())).Returns(42);

        myMock.Setup(iss => iss.DoThis("Henry", 0.0)).Throws<ArgumentException>();

        myMock.Setup(iss => iss.SomeProperty).Returns("FlickWidget");
        myMock.SetupSet(iss => iss.SomeProperty = "SlackWhacker");

        // ACT ...

        string result = $"The result is {myMock.Object.DoThis("Fred", 3.333)}";
        string result2 = $"The second result is {myMock.Object.DoThis("Joe", 4.44)}";

        // ASSERT ...

        Assert.AreEqual("The result is 42", result);
        Assert.AreEqual("The second result is 21", result2);
        myMock.Verify
            (iss => iss.DoThis(It.IsAny<string>(), It.IsAny<double>()), Times.Exactly(2));

        myMock.Verify(iss => iss.DoThis("Joe", It.IsAny<double>()), Times.Once());
        myMock.Verify(iss => iss.DoThis("Fred", 0.0), Times.Never());
    }
*/
}
