package org.initialde.yakasave.Unit;

import org.initialde.yakasave.Application.ContributeToSavingsFund;
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.ContributionAmountLessThanMinimumAuthorizedException;
import org.initialde.yakasave.Domain.Exceptions.IsNotContributorException;
import org.initialde.yakasave.Domain.Exceptions.SavingsFundClosedException;
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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrowsExactly;

public class ContributeToSavingsFundTests {

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

        var contributeAmount = 1000;

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

        var tokenGenerator = new FakeJwtService();

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

        var contributeToSavingsFund = new ContributeToSavingsFund(savingsFundRepository, authenticationGateway);
        contributeToSavingsFund.contribute(savingsFund, contributeAmount);

        var expectedSavingFundBalance = savingsFundRepository.findByReference(reference).get().getBalance();

        assertEquals(initialBalance + contributeAmount, expectedSavingFundBalance.toDouble());
    }

    @Test
    public void shouldFailToAddContributionInSavingsFundWhenAmountIsLessMinimumAmountAllowed() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var initialBalance = 3000;
        var contributor = UserFactory.create( "contributor", "1234");
        var savingsFund = SavingsFund.builder()
                .owner(owner)
                .reference(reference)
                .balance(initialBalance)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .deadline(LocalDate.now().plusDays(1))
                .type(TypeSavingsFund.COLLECTIVE)
                .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(contributor);

        var invalidContributionAmount = 50.0;

        var contributeToSavingsFund = new ContributeToSavingsFund(savingsFundRepository, authenticationGateway);

        assertThrowsExactly(ContributionAmountLessThanMinimumAuthorizedException.class, () -> contributeToSavingsFund.contribute(savingsFund, invalidContributionAmount));
    }

    @Test
    public void shouldFailToAddContributionInSavingsFundWhenIsNotAllowedContributor() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var initialBalance = 3000;
        var notAllowedContributor = UserFactory.create( "contributor", "1234");
        var savingsFund = SavingsFund.builder()
                .owner(owner)
                .reference(reference)
                .balance(initialBalance)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .deadline(LocalDate.now().plusDays(1))
                .type(TypeSavingsFund.COLLECTIVE)
                .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(notAllowedContributor);

        var contributionAmount = 5000.0;

        var contributeToSavingsFund = new ContributeToSavingsFund(savingsFundRepository, authenticationGateway);

        assertThrowsExactly(IsNotContributorException.class, () -> contributeToSavingsFund.contribute(savingsFund, contributionAmount));
    }

    @Test
    public void shouldFailToAddContributionWhenSavingsFundIsClosed() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var initialBalance = 3000;
        var contributor = UserFactory.create( "contributor", "1234");
        var savingsFund = SavingsFund.builder()
                .owner(owner)
                .reference(reference)
                .balance(initialBalance)
                .isActive(false)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .deadline(LocalDate.now().plusDays(1))
                .type(TypeSavingsFund.COLLECTIVE)
                .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(contributor);

        var contributionAmount = 5000.0;

        var contributeToSavingsFund = new ContributeToSavingsFund(savingsFundRepository, authenticationGateway);

        assertThrowsExactly(SavingsFundClosedException.class, () -> contributeToSavingsFund.contribute(savingsFund, contributionAmount));
    }

}
