package cz.muni.fi.pa165.core.company;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;

import java.util.List;
import java.util.Optional;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.openMocks;

/**
 * CompanyService unit tests
 *
 * @author xskacel
 */

public class CompanyServiceTest {

	@InjectMocks
	private CompanyService companyService;

	@Mock
	private CompanyRepository companyRepositoryMock;

	@BeforeEach
	void init() {
		openMocks(this);
	}

	@Test
	void shouldGetCompanyById() {
		Company company = Company
				.builder()
				.name("Bohemia")
				.build();

		when(companyRepositoryMock.findById(company.getId())).thenReturn(Optional.of(company));

		Company result = companyService.findById(company.getId());

		verify(companyRepositoryMock).findById(company.getId());

		assertEquals(company, result);
	}

	@Test
	void shouldCreateNewCompany() {
		Company company = Company
				.builder()
				.name("New Company")
				.build();

		when(companyRepositoryMock.save(company)).thenReturn(company);

		Company createdCompany = companyService.create(company);

		verify(companyRepositoryMock).save(company);

		assertThat(createdCompany).isEqualTo(company);
	}

	@Test
	void shouldFindCompanyByName() {
		Company company = Company.builder()
				.name("Bohemia")
				.build();

		when(companyRepositoryMock.findByName(company.getName())).thenReturn(Optional.of(company));

		Company result = companyService.findByName(company.getName());

		verify(companyRepositoryMock).findByName(company.getName());

		assertEquals(company, result);
	}

	@Test
	void shouldFindAllCompaniesPageableInt() {
		Company firstCompany = Company.builder()
				.name("Bohemia")
				.build();

		Company secondCompany = Company.builder()
				.name("New Company")
				.build();

		Page<Company> page = new PageImpl<>(List.of(firstCompany, secondCompany));

		int pageNumber = 0;
		int pageSize = 10;

		when(companyRepositoryMock.findAll(PageRequest.of(pageNumber, pageSize))).thenReturn(page);

		Page<Company> result = companyService.findAllPageableInt(pageNumber);

		verify(companyRepositoryMock).findAll(PageRequest.of(pageNumber, pageSize));

		assertThat(result).containsExactlyInAnyOrder(firstCompany, secondCompany);
	}

	@Test
	void shouldDeleteCompanyById() {
		Company company = Company.builder()
				.name("Bohemia")
				.build();

		when(companyRepositoryMock.findById(company.getId())).thenReturn(Optional.of(company));

		try {
			Company deletedCompany = companyService.deleteById(company.getId());

			verify(companyRepositoryMock).findById(company.getId());
			verify(companyRepositoryMock).save(company); // soft delete

			assertNotNull(deletedCompany.getDeletedAt());
			assertEquals(company, deletedCompany);
		} catch (Exception e){
			Assertions.assertThat(false).isTrue();
		}
	}

	@Test
	void shouldUpdateCompany() {
		Company originalCompany = Company.builder()
				.name("Bohemia")
				.build();

		Company updatedCompany = Company.builder()
				.name("New Name")
				.build();
		
		when(companyRepositoryMock.findById(originalCompany.getId())).thenReturn(Optional.of(originalCompany));

		when(companyRepositoryMock.save(updatedCompany)).thenReturn(updatedCompany);

		Company result = companyService.update(updatedCompany, originalCompany.getId());

		verify(companyRepositoryMock).findById(originalCompany.getId());
		verify(companyRepositoryMock).save(updatedCompany);

		assertEquals(originalCompany.getId(), result.getId());
		assertEquals(updatedCompany.getName(), result.getName());
	}
	@Test
	void shouldHardDeleteAllCompanies() {
		companyService.hardDeleteAll();
		verify(companyRepositoryMock).deleteAll();
	}

	@Test
	void shouldDeleteAllCompanies() {
		var data = List.of(
				Company.builder().name("Bohemia").build(),
				Company.builder().name("Apple").build()
		);
		when(companyRepositoryMock.findAll()).thenReturn(data);
		companyService.deleteAll();
		verify(companyRepositoryMock).findAll();
		verify(companyRepositoryMock).saveAll(data);
	}
	@Test
	void shouldDeleteAllCompaniesByIds() {
		Company company1 = Company.builder().name("Bohemia").build();
		Company company2 = Company.builder().name("Apple").build();
		String[] ids = {company1.getId(), company2.getId()};
		when(companyRepositoryMock.findById(ids[0])).thenReturn(Optional.of(company1));
		when(companyRepositoryMock.findById(ids[1])).thenReturn(Optional.of(company2));

		try{
		companyService.deleteAllById(ids);

		verify(companyRepositoryMock).save(company1);
		verify(companyRepositoryMock).save(company2);
		} catch (Exception e){
			Assertions.assertThat(false).isTrue();
		}
	}
	@Test
	void shouldFindAllCompanies() {
		List<Company> companies = List.of(
				Company.builder().name("Bohemia").build(),
				Company.builder().name("Apple").build()
		);

		when(companyRepositoryMock.findAll()).thenReturn(companies);
		List<Company> result = companyService.findAll();
		verify(companyRepositoryMock).findAll();
		assertEquals(companies, result);
	}
	@Test
	void shouldFindAllCompaniesPageable() {
		int pageNumber = 0;
		int pageSize = 10;
		Pageable pageable = PageRequest.of(pageNumber, pageSize);

		List<Company> companies = List.of(
				Company.builder().name("Bohemia").build(),
				Company.builder().name("Apple").build()
		);

		Page<Company> pageResult = new PageImpl<>(companies, pageable, companies.size());
		when(companyRepositoryMock.findAll(pageable)).thenReturn(pageResult);

		Page<Company> result = companyService.findAllPageable(pageable);
		verify(companyRepositoryMock).findAll(pageable);

		assertEquals(pageResult, result);
	}
}
