package org.initialde.yakasave.Unit;

import org.initialde.yakasave.Application.JoinSavingsFund;
import org.initialde.yakasave.Domain.Entities.SavingsFund;

import org.initialde.yakasave.Domain.Entities.User;
import org.initialde.yakasave.UserFactory;
import org.initialde.yakasave.Domain.Enums.TypeSavingsFund;
import org.initialde.yakasave.Domain.Exceptions.AlreadyContributorException;
import org.initialde.yakasave.Domain.Exceptions.ExceedMaxMembersAllowedException;
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.Collection;
import java.util.List;
import java.util.UUID;

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

public class JoinSavingFundTests {
    @Test
    public void shouldJoinSavingFund() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var contributor = UserFactory.create( "contributor", "1234");
        int maxAllowedMembers = 2;

        var tokenGenerator = new FakeJwtService();

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

        var savingsFund = SavingsFundFactory.createCollectiveSavingsFund(reference, owner, maxAllowedMembers);
        var savingsFundRepository = new InMemorySavingsFundRepository();
        savingsFundRepository.save(savingsFund);

        var joinSavingFund = new JoinSavingsFund(savingsFundRepository, authenticationGateway);
        joinSavingFund.join(savingsFund);

        var expectedSavingFundMembers = savingsFundRepository.findByReference(reference).get().getContributors();
        assertEquals(1, expectedSavingFundMembers.size());
    }

    @Test
    public void shouldNotJoinSavingsFundWhenMaxAllowedMembersIsReached() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var contributor1 = UserFactory.create( "contributor1", "1234");
        var contributor2 = UserFactory.create( "contributor2", "1234");
        var newContributor = UserFactory.create( "new_contributor", "1234");
        Collection<User> contributors = new ArrayList<>(List.of(contributor1, contributor2));
        int maxAllowedMembers = 2;

        var tokenGenerator = new FakeJwtService();

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

        var savingsFund = SavingsFund.builder()
                .reference(reference)
                .owner(owner)
                .maxAllowedMembers(maxAllowedMembers)
                .type(TypeSavingsFund.COLLECTIVE)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .deadline(LocalDate.now().plusDays(1))
                .contributors(contributors)
                .build();
        var savingsFundRepository = new InMemorySavingsFundRepository();
        savingsFundRepository.save(savingsFund);

        var joinSavingFund = new JoinSavingsFund(savingsFundRepository, authenticationGateway);
        var expectedSavingFundMembers = savingsFundRepository.findByReference(reference).get().getContributors();

        assertThrowsExactly(ExceedMaxMembersAllowedException.class, () -> joinSavingFund.join(savingsFund));
        assertEquals(maxAllowedMembers, expectedSavingFundMembers.size());
    }

    @Test
    public void shouldNotJoinSavingsFundWhenIsAlreadyContributor() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var contributor = UserFactory.create( "contributor1", "1234");
        var alreadyContributor = UserFactory.create( "contributor2", "1234");
        Collection<User> contributors = new ArrayList<>(List.of(contributor, alreadyContributor));
        int maxAllowedMembers = 3;

        var tokenGenerator = new FakeJwtService();

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

        var savingsFund = SavingsFund.builder()
                .reference(reference)
                .owner(owner)
                .maxAllowedMembers(maxAllowedMembers)
                .type(TypeSavingsFund.COLLECTIVE)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .deadline(LocalDate.now().plusDays(1))
                .contributors(contributors)
                .build();
        var savingsFundRepository = new InMemorySavingsFundRepository();
        savingsFundRepository.save(savingsFund);

        var joinSavingFund = new JoinSavingsFund(savingsFundRepository, authenticationGateway);
        var expectedSavingFundMembers = savingsFundRepository.findByReference(reference).get().getContributors();


        assertThrowsExactly(AlreadyContributorException.class, () -> joinSavingFund.join(savingsFund));
        assertEquals(2, expectedSavingFundMembers.size());
    }

}
