package org.initialde.yakasave.Unit;

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

import org.initialde.yakasave.UserFactory;
import org.initialde.yakasave.Domain.Entities.WithdrawalApproval;
import org.initialde.yakasave.Domain.Enums.TypeSavingsFund;
import org.initialde.yakasave.Domain.Exceptions.AlreadyApprovedWithdrawException;
import org.initialde.yakasave.Domain.Exceptions.NotYetContributedException;
import org.initialde.yakasave.Domain.Exceptions.OwnerCannotGiveApprovalException;
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 ApproveWithdrawFromSavingsFundTest {
    @Test
    public void shouldApproveWithdrawFromSavingsFunWhichNeedsApproval() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var contributor = UserFactory.create( "Contributor", "1234");
        var contributionOfContributor = new Contribution(contributor, 1000.0);
        var savingsFund = SavingsFund
                .builder()
                .reference(reference)
                .owner(owner)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .deadline(LocalDate.now().plusDays(1))
                .needsApproval(true)
                .type(TypeSavingsFund.COLLECTIVE)
                .maxAllowedMembers(3)
                .contributors(new ArrayList<>(List.of(owner, contributor)))
                .contributions(new ArrayList<>(List.of(contributionOfContributor)))
                .build();

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

        var tokenGenerator = new FakeJwtService();

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

        var approveWithdrawFromSavingsFun = new ApproveWithdrawFromSavingsFund(savingsFunRepository, authenticationGateway);
        approveWithdrawFromSavingsFun.approve(savingsFund);

        var actualSavingsFundReceiptApprovals = savingsFunRepository.findByReference(reference).get().getReceiptApprovals();

        assertEquals(1, actualSavingsFundReceiptApprovals.size());
    }

    @Test
    public void shouldFailApproveWithdrawFromSavingsFunWhichNeedsApprovalWhenContributorNotYetContributed() {
        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)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .deadline(LocalDate.now().plusDays(1))
                .needsApproval(true)
                .type(TypeSavingsFund.COLLECTIVE)
                .maxAllowedMembers(3)
                .contributors(new ArrayList<>(List.of(owner, contributor)))
                .build();

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

        var tokenGenerator = new FakeJwtService();

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

        var approveWithdrawFromSavingsFun = new ApproveWithdrawFromSavingsFund(savingsFunRepository, authenticationGateway);

        assertThrowsExactly(NotYetContributedException.class, () -> approveWithdrawFromSavingsFun.approve(savingsFund));
    }

    @Test
    public void shouldFailApproveWithdrawFromSavingsFunWhichNeedsApprovalWhenContributorAlreadyApproved() {
        var reference = UUID.randomUUID().toString();
        var owner = UserFactory.create( "owner", "1234");
        var contributor = UserFactory.create( "Contributor", "1234");
        var contributionOfContributor = new Contribution(contributor, 1000.0);
        var contributorApproval = new WithdrawalApproval(contributor);
        var savingsFund = SavingsFund
                .builder()
                .reference(reference)
                .owner(owner)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .deadline(LocalDate.now().plusDays(1))
                .needsApproval(true)
                .type(TypeSavingsFund.COLLECTIVE)
                .maxAllowedMembers(3)
                .contributors(new ArrayList<>(List.of(owner, contributor)))
                .contributions(new ArrayList<>(List.of(contributionOfContributor)))
                .receiptApprovals(new ArrayList<>(List.of(contributorApproval)))
                .build();

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

        var tokenGenerator = new FakeJwtService();

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

        var approveWithdrawFromSavingsFun = new ApproveWithdrawFromSavingsFund(savingsFunRepository, authenticationGateway);

        assertThrowsExactly(AlreadyApprovedWithdrawException.class, () -> approveWithdrawFromSavingsFun.approve(savingsFund));
    }

    @Test
    public void shouldFailApproveWithdrawFromSavingsFunWhichNeedsApprovalWhenContributorIsOwner() {
        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)
                .goalAmount(Amount.MINIMUM_GOAL_AMOUNT)
                .deadline(LocalDate.now().plusDays(1))
                .needsApproval(true)
                .type(TypeSavingsFund.COLLECTIVE)
                .maxAllowedMembers(3)
                .contributors(new ArrayList<>(List.of(owner, contributor)))
                .build();

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

        var tokenGenerator = new FakeJwtService();

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

        var approveWithdrawFromSavingsFun = new ApproveWithdrawFromSavingsFund(savingsFunRepository, authenticationGateway);

        assertThrowsExactly(OwnerCannotGiveApprovalException.class, () -> approveWithdrawFromSavingsFun.approve(savingsFund));
    }
}
