package com.ufund.api.ufundapi.controller;

import java.io.IOException;
import java.util.Map;
import static java.util.Map.entry;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.springframework.http.HttpStatus;

import com.ufund.api.ufundapi.DuplicateKeyException;
import com.ufund.api.ufundapi.model.Need;
import com.ufund.api.ufundapi.model.Need.GoalType;
import com.ufund.api.ufundapi.service.CupboardService;

public class CupboardControllerTest {
    private CupboardController cupboardController;
    private CupboardService mockCupboardService;

    @BeforeEach
    public void setupCupboardDAO() {
        mockCupboardService = mock(CupboardService.class);
        cupboardController = new CupboardController(mockCupboardService);
    }

    @Test
    public void createNeed() throws IOException, DuplicateKeyException {
        String name = "Test";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        var need = new Need(name, type, maxGoal);
        when(mockCupboardService.createNeed(name, maxGoal, type)).thenReturn(need);


        Map<String, String> needMap = Map.ofEntries(
                entry("name", "Test"),
                entry("maxGoal", "100"),
                entry("goalType", "MONETARY")
        );

        var res = cupboardController.createNeed(needMap);

        assertEquals(HttpStatus.OK, res.getStatusCode());
        assertEquals(need, res.getBody());
    }

    @Test
    public void createNeedBadMaxGoal() throws IOException, DuplicateKeyException {
        when(mockCupboardService.createNeed("Name", -100, Need.GoalType.MONETARY)).thenThrow(new IllegalArgumentException());

        Map<String, String> needMap = Map.ofEntries(
                entry("name", "Name"),
                entry("maxGoal", "-100"),
                entry("goalType", "MONETARY"));

        var res = cupboardController.createNeed(needMap);

        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, res.getStatusCode());
    }

    @Test
    public void createNeedIOException() throws IOException, DuplicateKeyException {
        when(mockCupboardService.createNeed("Name", 100, Need.GoalType.MONETARY)).thenThrow(new IOException());

        Map<String, String> needMap = Map.ofEntries(
                entry("name", "Name"),
                entry("maxGoal", "100"),
                entry("goalType", "MONETARY"));

        var res = cupboardController.createNeed(needMap);

        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
    }

    @Test
    public void getNeeds() throws IOException {
        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
        when(mockCupboardService.getNeeds()).thenReturn(new Need[]{need});

        var res = cupboardController.getNeeds();

        assertEquals(HttpStatus.OK, res.getStatusCode());
        assertArrayEquals(new Need[]{need}, res.getBody());
    }

    @Test
    public void getNeedsIOException() throws IOException {
        when(mockCupboardService.getNeeds()).thenThrow(new IOException());

        var res = cupboardController.getNeeds();

        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
    }

    @Test
    public void getNeedsEmpty() throws IOException {
        when(mockCupboardService.getNeeds()).thenReturn(new Need[]{});

        var res = cupboardController.getNeeds();

        assertEquals(HttpStatus.OK, res.getStatusCode());
        assertArrayEquals(new Need[]{}, res.getBody());
    }

    @Test
    public void searchNeeds() throws IOException {
        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
        when(mockCupboardService.searchNeeds("Na")).thenReturn(new Need[]{need});

        var res = cupboardController.searchNeeds("Na");

        assertEquals(HttpStatus.OK, res.getStatusCode());
        assertArrayEquals(new Need[]{need}, res.getBody());
    }

    @Test
    public void searchNeedsIOException() throws IOException {
        when(mockCupboardService.searchNeeds("Na")).thenThrow(new IOException());

        var res = cupboardController.searchNeeds("Na");

        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
    }

    @Test
    public void searchNeedsEmpty() throws IOException {
        when(mockCupboardService.searchNeeds("Na")).thenReturn(new Need[]{});

        var res = cupboardController.searchNeeds("Na");

        assertEquals(HttpStatus.OK, res.getStatusCode());
        assertArrayEquals(new Need[]{}, res.getBody());
    }

    @Test
    public void getNeed() throws IOException {
        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
        when(mockCupboardService.getNeed(need.getId())).thenReturn(need);

        var res = cupboardController.getNeed(need.getId());

        assertEquals(HttpStatus.OK, res.getStatusCode());
        assertEquals(need, res.getBody());
    }

    @Test
    public void getNeedIOException() throws IOException {
        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
        when(mockCupboardService.getNeed(need.getId())).thenThrow(new IOException());

        var res = cupboardController.getNeed(need.getId());

        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
    }

    @Test
    public void getNeedFail() throws IOException {
        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
        when(mockCupboardService.getNeed(need.getId())).thenReturn(null);

        var res = cupboardController.getNeed(need.getId());

        assertEquals(HttpStatus.NOT_FOUND, res.getStatusCode());
        assertNull(res.getBody());
    }

    @Test
    public void updateNeeds() throws IOException {
        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
        when(mockCupboardService.updateNeed(need, 1)).thenReturn(need);

        var res = cupboardController.updateNeed(need, 1);

        assertEquals(HttpStatus.OK, res.getStatusCode());
        assertEquals(need, res.getBody());
    }

    @Test
    public void updateNeedsIOException() throws IOException {
        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
        when(mockCupboardService.updateNeed(need, 1)).thenThrow(new IOException());

        var res = cupboardController.updateNeed(need, 1);

        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
    }

    @Test
    public void deleteNeed() throws IOException {
        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
        when(mockCupboardService.getNeed(1)).thenReturn(need);
        when(mockCupboardService.deleteNeed(1)).thenReturn(true);

        var res = cupboardController.deleteNeed(1);

        assertEquals(HttpStatus.OK, res.getStatusCode());
    }

    @Test
    public void deleteNeedFail() throws IOException {
        when(mockCupboardService.getNeed(1)).thenReturn(null);
        when(mockCupboardService.deleteNeed(1)).thenReturn(false);

        var res = cupboardController.deleteNeed(1);

        assertEquals(HttpStatus.NOT_FOUND, res.getStatusCode());
    }

    @Test
    public void deleteNeedIOException() throws IOException {
        var need = new Need("Name", 1, 100, Need.GoalType.MONETARY);
        when(mockCupboardService.getNeed(1)).thenReturn(need);
        when(mockCupboardService.deleteNeed(1)).thenThrow(new IOException());

        var res = cupboardController.deleteNeed(1);

        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, res.getStatusCode());
    }
}