package com.uca.bookings;

import        org.junit.jupiter.api.Test;
import        java.util.concurrent.Callable;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.jupiter.api.Assertions.assertThrows;
import        org.joda.money.Money;
import        com.uca.flights.*;
import        java.util.*;
import        java.time.*;

public class TestBooking
{
	public static void testCustomer()
	{

		//Construtor
		assertThat(exceptionOf(() -> new Customer(null,null,null,null,null)), instanceOf(IllegalArgumentException.class));
		PMT pmtest = new PMT();
		assertThat(exceptionOf(() -> new Customer(null,"null","null","null",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("   ","null","null","null",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("	","null","null","null",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null",null,"null","null",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","  ","null","null",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","	","null","null",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","null",null,"null",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","null","  ","null",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","null","	","null",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","null","rgedrgsergseg","null",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","null","null",null,pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","null","null","  ",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","null","null","	",pmtest)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","null","null","null",null)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Customer("null","null","null","fzrgzef",null)), instanceOf(IllegalArgumentException.class));

		//Getter, setter
		assertThat(exceptionOf(() -> new Customer("null","null","test@test.com","null",pmtest).getId()), instanceOf(UUID.class));
		assertThat(exceptionOf(() -> new Customer("null","null","test@test.com","null",pmtest).getEmail()), equalTo("null"));
		assertThat(exceptionOf(() -> new Customer("null","null","test@test.com","null",pmtest).getPaymentMethod()), equalTo(pmtest));
		assertThat(exceptionOf(() -> new Customer("null","null","test@test.com","null",pmtest).getLastName()), equalTo("null"));
		assertThat(exceptionOf(() -> new Customer("null","null","test@test.com","null",pmtest).getFirstName()), equalTo("null"));

		Customer c1 = new Customer("null","null","test@test.com","null",pmtest);
		Customer c2 = new Customer("null","null","test@test.com","null",pmtest);
		assertThat(c1.getId(), not(c2.getId()));

		assertThrows(IllegalArgumentException.class, () -> c1.setPaymentMethod(null));

		c1.setEmail("test");
		assertThat(exceptionOf(() -> c1.getEmail()), equalTo("test"));

		assertThat(c1.toString(), equalTo("null null"));
		assertThat(c1.hashCode(), equalTo(c1.getId().hashCode()));
	}

	public static void testPassenger()
	{
		ZonedDateTime date1 = ZonedDateTime.now();
		ZonedDateTime date2 = date1.minus(Duration.ofDays(70));
		//Construtor
		assertThat(exceptionOf(() -> new Passenger(null,null,null,null,null,null,null)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger(null,"null",date2,"null","null","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("   ","null",date2,"null","null","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("	","null",date2,"null","null","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null",null,date2,"null","null","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","  ",date2,"null","null","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","	",date2,"null","null","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",null,"null","null","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date1,"null","null","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,null,"null","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"  ","null","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"	","null","null","null")), instanceOf(IllegalArgumentException.class));	
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null",null,"null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","  ","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","	","null","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null",null,"null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","  ","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","	","null")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null",null)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null","  ")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null","	")), instanceOf(IllegalArgumentException.class));

		//Getter, setter
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null","null").getId()), instanceOf(UUID.class));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null","null").getGender()), equalTo("null"));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null","null").getNationality()), equalTo("null"));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null","null").getPassportNumber()), equalTo("null"));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null","null").getCountryOfResidence()), equalTo("null"));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null","null").getBirthdate()), equalTo(date2));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null","null").getLastName()), equalTo("null"));
		assertThat(exceptionOf(() -> new Passenger("null","null",date2,"null","null","null","null").getFirstName()), equalTo("null"));

		Passenger p1 = new Passenger("null","null",date2,"null","null","null","null");
		Passenger p2 = new Passenger("null","null",date2,"null","null","null","null");
		assertThat(p1.getId(), not(p2.getId()));

		assertThrows(IllegalArgumentException.class, () -> p1.setGender(null));
		assertThrows(IllegalArgumentException.class, () -> p1.setGender("	"));
		assertThrows(IllegalArgumentException.class, () -> p1.setGender("  "));
		assertThrows(IllegalArgumentException.class, () -> p1.setCountryOfResidence(null));
		assertThrows(IllegalArgumentException.class, () -> p1.setCountryOfResidence("	"));
		assertThrows(IllegalArgumentException.class, () -> p1.setCountryOfResidence("  "));
		assertThrows(IllegalArgumentException.class, () -> p1.setPassportNumber(null));
		assertThrows(IllegalArgumentException.class, () -> p1.setPassportNumber("	"));
		assertThrows(IllegalArgumentException.class, () -> p1.setPassportNumber("  "));

		p1.setGender("test");
		assertThat(exceptionOf(() -> p1.getGender()), equalTo("test"));
		p1.setCountryOfResidence("test");
		assertThat(exceptionOf(() -> p1.getCountryOfResidence()), equalTo("test"));
		p1.setPassportNumber("test");
		assertThat(exceptionOf(() -> p1.getPassportNumber()), equalTo("test"));

		assertThrows(IllegalArgumentException.class, () -> p1.addFidelityPoint(-5));
		assertThrows(IllegalArgumentException.class, () -> p1.removeFidelityPoint(-5));
		assertThat(exceptionOf(() -> p1.getFidelityPoint()), equalTo(0));
		assertThrows(IllegalArgumentException.class, () -> p1.addFidelityPoint(5));
		assertThat(exceptionOf(() -> p1.getFidelityPoint()), equalTo(5));
		assertThrows(IllegalArgumentException.class, () -> p1.removeFidelityPoint(3));
		assertThat(exceptionOf(() -> p1.getFidelityPoint()), equalTo(2));

		assertThat(p1.toString(), equalTo("null null"));
		assertThat(p1.hashCode(), equalTo(p1.getId().hashCode()));
	}

	public static void testBooking()
	{
		ZonedDateTime date = ZonedDateTime.now();
		ZonedDateTime date2 = date.minus(Duration.ofDays(70));
		PMT pmtest = new PMT();
		Money mon = Money.parse("USD 25");
		Airport a1 = new Airport(new City(new CityId("c1"),"t1"), "ae1","n1");
		Airport a2 = new Airport(new City(new CityId("c2"),"t2"), "ae2","n2");
		Step s1 = new Step(a1,Duration.ZERO);
		Step s2 = new Step(a2,Duration.ofDays(1));
		Step s3 = new Step(a1,Duration.ofDays(2));
		Jump j2 = new Jump(s2,s3);
		Jump j1 = new Jump(s1,s2,j2);
		Trip t = new Trip(j1);
		Company comp = new Company("GWK","Gwak-Airline");
		comp.addFlight(date,t); 
		ArrayList<FlightId> m = comp.getFlights();
		Flight f = comp.getFlight(m.get(0));
		Passenger p = new Passenger("null","null",date2,"null","null","null","null");
		Customer c = new Customer("null","null","test@test.com","null",pmtest);

		//Constructor 1
		assertThat(exceptionOf(() -> Booking.createBooking(null,null,null,null)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,c,p)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(null,f,c,p)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,null,c,p)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,null,p)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,c,null)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,c,p)), instanceOf(IllegalArgumentException.class));
		comp.open(m.get(0));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,c,p)), instanceOf(Booking.class));

		//Constructor 2
		comp.close(m.get(0));
		assertThat(exceptionOf(() -> Booking.createBooking(null,null,null,null,null)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,c,p,null)), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(null,f,c,p,"test")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,null,c,p,"test")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,null,p,"test")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,c,null,"test")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,c,p,"test")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,c,p,"  ")), instanceOf(IllegalArgumentException.class));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,c,p,"	")), instanceOf(IllegalArgumentException.class));
		comp.open(m.get(0));
		assertThat(exceptionOf(() -> Booking.createBooking(mon,f,c,p,"test")), instanceOf(Booking.class));

		comp.addFlight(date,t);
		Flight f2 = comp.getFlight(comp.getFlights().get(1));
		comp.open(m.get(1));
		Booking b = Booking.createBooking(mon,f2,c,p);
		Booking b2 = Booking.createBooking(mon,f2,c,p,"test");

		//Getter
		assertThat(b.getId(),not(equalTo(b2.getId())));
		assertThat(b.getFlight(),equalTo(f2));
		assertThat(b.getPassenger(),equalTo(p));
		assertThat(b.getCustomer(),equalTo(c));
		assertThat(b.getDate(),equalTo(date));
		assertThat(b.getSeatNumber(),equalTo(null));
		b.setSeatNumber("test");
		assertThat(b.getSeatNumber(),equalTo("test"));
		assertThat(b2.getSeatNumber(),equalTo("test"));

		assertThat(b.hashCode(),equalTo(b.getId().hashCode()));


	}

	public static void testStates()
	{
		//Constructor
		State state1 = BookingState.INITIAL;
		State state2 = BookingState.INITIAL;		
		assertThat(state1, equalTo(state2));
		state1 = BookingState.PAYED;
		state2 = BookingState.PAYED;		
		assertThat(state1, equalTo(state2));
		state1 = BookingState.CONFIRMED;
		state2 = BookingState.CONFIRMED;		
		assertThat(state1, equalTo(state2));
		state1 = BookingState.CANCELED;
		state2 = BookingState.CANCELED;		
		assertThat(state1, equalTo(state2));

		ZonedDateTime date = ZonedDateTime.now();
		ZonedDateTime date2 = date.minus(Duration.ofDays(70));
		PMT pmtest = new PMT();
		Money mon = Money.parse("USD 25");
		Airport a1 = new Airport(new City(new CityId("c1"),"t1"), "ae1","n1");
		Airport a2 = new Airport(new City(new CityId("c2"),"t2"), "ae2","n2");
		Step s1 = new Step(a1,Duration.ZERO);
		Step s2 = new Step(a2,Duration.ofDays(1));
		Step s3 = new Step(a1,Duration.ofDays(2));
		Jump j2 = new Jump(s2,s3);
		Jump j1 = new Jump(s1,s2,j2);
		Trip t = new Trip(j1);
		Company comp = new Company("GWK","Gwak-Airline");
		comp.addFlight(date,t); 
		ArrayList<FlightId> m = comp.getFlights();
		Flight f = comp.getFlight(m.get(0));
		Passenger p = new Passenger("null","null",date2,"null","null","null","null");
		Customer c = new Customer("null","null","test@test.com","null",pmtest);
		comp.close(m.get(0));
		assertThrows(UnsupportedOperationException.class, () -> Booking.createBooking(mon,f,c,p));
		comp.open(m.get(0));

		//Initial state
		final Booking b1 = Booking.createBooking(mon,f,c,p);
		state1 = BookingState.INITIAL;
		assertThat(state1, equalTo(b1.getState()));
		assertThrows(UnsupportedOperationException.class, () -> b1.confirm());
		b1.setState(state1);
		b1.pay();
		assertThat(b1.getState(), equalTo(BookingState.PAYED));
		b1.setState(state1);
		b1.cancel();
		assertThat(b1.getState(), equalTo(BookingState.CANCELED));
		assertThat(b1.getPassenger().getBookings().contains(b1), equalTo(false));
		assertThat(b1.getCustomer().getBookings().contains(b1), equalTo(false));
		assertThat(b1.getFlight().getBookings().contains(b1), equalTo(false));

		//Payed state
		final Booking b2 = Booking.createBooking(mon,f,c,p);
		state1 = BookingState.PAYED;
		b2.setState(state1);
		b2.confirm();
		assertThat(b2.getState(), equalTo(BookingState.CONFIRMED));
		b2.setState(state1);
		b2.pay();
		assertThat(b2.getState(), equalTo(BookingState.PAYED));
		b2.setState(state1);
		b2.cancel();
		assertThat(b2.getPassenger().getBookings().contains(b2), equalTo(false));
		assertThat(b2.getCustomer().getBookings().contains(b2), equalTo(false));
		assertThat(b2.getFlight().getBookings().contains(b2), equalTo(false));
		assertThat(b2.getState(), equalTo(BookingState.CANCELED));

		//Confirm state
		final Booking b3 = Booking.createBooking(mon,f,c,p);
		state1 = BookingState.CONFIRMED;
		b3.setState(state1);
		b3.confirm();
		assertThat(b3.getState(), equalTo(state1));
		assertThrows(UnsupportedOperationException.class, () -> b3.pay());
		b3.cancel();
		assertThat(b3.getPassenger().getBookings().contains(b3), equalTo(false));
		assertThat(b3.getCustomer().getBookings().contains(b3), equalTo(false));
		assertThat(b3.getFlight().getBookings().contains(b3), equalTo(false));
		assertThat(b3.getState(), equalTo(BookingState.CANCELED));

		//Cancel state
		final Booking b4 = Booking.createBooking(mon,f,c,p);
		state1 =BookingState.CANCELED;
		b4.setState(state1);
		assertThrows(UnsupportedOperationException.class, () -> b4.confirm());
		assertThrows(UnsupportedOperationException.class, () -> b4.pay());
		b4.cancel();
		assertThat(b4.getPassenger().getBookings().contains(b4), equalTo(false));
		assertThat(b4.getCustomer().getBookings().contains(b4), equalTo(false));
		assertThat(b4.getFlight().getBookings().contains(b4), equalTo(false));
		assertThat(b4.getState(), equalTo(BookingState.CANCELED));
	}

	private static class PMT implements PaymentMethod
	{
		PMT()
		{

		}

		public void refund(Money money)
		{

		}

		public void charge(Money money)
		{

		}

		public PaymentMethod hide()
		{
			return this;
		}
	}

	public static Throwable exceptionOf(Callable<?> callable) {
		try {
			callable.call();
			return null;
		} catch (Throwable t) {
			return t;
		}
	}
}