package org.initialde.yakasave.Unit;

import org.initialde.yakasave.Application.RemoveContributorFromSavingsFund;
import org.initialde.yakasave.Domain.Entities.Contribution;
import org.initialde.yakasave.Domain.Entities.SavingsFund;

import org.initialde.yakasave.UserFactory;
import org.initialde.yakasave.Domain.Enums.TypeSavingsFund;
import org.initialde.yakasave.Domain.Exceptions.CannotRemoveContributorException;
import org.initialde.yakasave.Domain.Exceptions.CannotRemoveOwnerException;
import org.initialde.yakasave.Domain.Exceptions.IsNotContributorException;
import org.initialde.yakasave.Domain.ValueObject.Amount;
import org.initialde.yakasave.Infrastructure.Persistence.Fake.InMemorySavingsFundRepository;
import org.initialde.yakasave.Infrastructure.authentication.FakeAuthenticationGateway;
import org.initialde.yakasave.Infrastructure.authentication.Jwt.fake.FakeJwtService;
import org.junit.jupiter.api.Test;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;

public class RemoveContributorFromSavingsFundTest {
    @Test
    public void shouldRemoveContributorFromSavingsFundWhenHasNotContributed() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var contributor = UserFactory.create( "contributor", "1234");
        var savingsFund = SavingsFund
                .builder()
                .reference(reference)
                .owner(owner)
                .balance(3000)
                .isActive(true)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .type(TypeSavingsFund.COLLECTIVE)
                .deadline(LocalDate.now().plusDays(1))
                .contributors(new ArrayList<>(List.of(owner, contributor)))
                .build();

        var savingsFundRepository = new InMemorySavingsFundRepository();
        savingsFundRepository.save(savingsFund);

        var tokenGenerator = new FakeJwtService();

        var authenticationGateway = new FakeAuthenticationGateway(tokenGenerator);
        authenticationGateway.authenticate(owner);

        var removeContributorFromSavingsFund = new RemoveContributorFromSavingsFund(savingsFundRepository,
                authenticationGateway);
        removeContributorFromSavingsFund.remove(savingsFund, contributor);

        var actualContributors = savingsFundRepository.findByReference(reference).get().getContributors();
        assertFalse(actualContributors.contains(contributor));
    }

    @Test
    public void shouldFailRemoveContributorFromSavingsWhenIsNotContributor() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var contributor = UserFactory.create( "contributor", "1234");
        var savingsFund = SavingsFund
                .builder()
                .reference(reference)
                .owner(owner)
                .balance(3000)
                .isActive(true)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .type(TypeSavingsFund.COLLECTIVE)
                .contributors(new ArrayList<>(List.of(owner)))
                .deadline(LocalDate.now().plusDays(1))
                .build();

        var savingsFundRepository = new InMemorySavingsFundRepository();
        savingsFundRepository.save(savingsFund);

        var tokenGenerator = new FakeJwtService();

        var authenticationGateway = new FakeAuthenticationGateway(tokenGenerator);
        authenticationGateway.authenticate(owner);

        var removeContributorFromSavingsFund = new RemoveContributorFromSavingsFund(savingsFundRepository,
                authenticationGateway);

        assertThrowsExactly(IsNotContributorException.class, () -> removeContributorFromSavingsFund.remove(savingsFund, contributor));
    }

    @Test
    public void shouldFailRemoveContributorFromSavingsWhenHasContributed() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var contributor = UserFactory.create( "contributor", "1234");
        var savingsFund = SavingsFund
                .builder()
                .reference(reference)
                .owner(owner)
                .balance(3000)
                .isActive(true)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .type(TypeSavingsFund.COLLECTIVE)
                .deadline(LocalDate.now().plusDays(1))
                .contributors(new ArrayList<>(List.of(owner, contributor)))
                .contributions(new ArrayList<>(List.of(new Contribution(contributor, 1000.0))))
                .build();

        var savingsFundRepository = new InMemorySavingsFundRepository();
        savingsFundRepository.save(savingsFund);

        var tokenGenerator = new FakeJwtService();

        var authenticationGateway = new FakeAuthenticationGateway(tokenGenerator);
        authenticationGateway.authenticate(owner);

        var removeContributorFromSavingsFund = new RemoveContributorFromSavingsFund(savingsFundRepository,
                authenticationGateway);

        assertThrowsExactly(CannotRemoveContributorException.class, () -> removeContributorFromSavingsFund.remove(savingsFund, contributor));
    }

    @Test
    public void shouldFailRemoveContributorFromSavingsFundWhenContributorIsOwner() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var savingsFund = SavingsFund
                .builder()
                .reference(reference)
                .owner(owner)
                .balance(3000)
                .isActive(true)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .type(TypeSavingsFund.COLLECTIVE)
                .deadline(LocalDate.now().plusDays(1))
                .contributors(new ArrayList<>(List.of(owner)))
                .build();

        var savingsFundRepository = new InMemorySavingsFundRepository();
        savingsFundRepository.save(savingsFund);

        var tokenGenerator = new FakeJwtService();

        var authenticationGateway = new FakeAuthenticationGateway(tokenGenerator);
        authenticationGateway.authenticate(owner);

        var removeContributorFromSavingsFund = new RemoveContributorFromSavingsFund(savingsFundRepository,
                authenticationGateway);

        assertThrowsExactly(CannotRemoveOwnerException.class, () -> removeContributorFromSavingsFund.remove(savingsFund, owner));
    }
}
