package com.ufund.api.ufundapi.controller;

import java.io.IOException;
import java.util.List;
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 static org.mockito.Mockito.*;

import com.ufund.api.ufundapi.service.AuthService;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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;

class CupboardControllerTest {
    private CupboardController cupboardController;
    private CupboardService mockCupboardService;
    private final String key = "dummyKey";
    private AuthService mockAuthService;

    @BeforeEach
    void setupCupboardDAO() {
        mockAuthService = mock(AuthService.class);
        mockCupboardService = mock(CupboardService.class);
        cupboardController = new CupboardController(mockCupboardService, mockAuthService);

        try {
            doThrow().when(mockAuthService).keyHasAccessToCupboard(key);
        } catch (Exception ignored) {}
    }

    @Test
    void createNeed() throws IOException, DuplicateKeyException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.createNeed(name, image, location, maxGoal, type, urgent, description)).thenReturn(need);


        Map<String, Object> needMap = Map.ofEntries(
                entry("name", "Test"),
                entry("image", ""),
                entry("location", "Atlantis"),
                entry("maxGoal", 100),
                entry("type", "MONETARY"),
                entry("urgent", false),
                entry("description", "")
        );

        var res = cupboardController.createNeed(needMap, key);

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

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

        Map<String, Object> needMap = Map.ofEntries(
                entry("name", "Test"),
                entry("image", ""),
                entry("location", "Atlantis"),
                entry("maxGoal", -100),
                entry("type", "MONETARY"),
                entry("urgent", false),
                entry("description", "")
        );

        var res = cupboardController.createNeed(needMap, key);

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

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

        Map<String, Object> needMap = Map.ofEntries(
                entry("name", "Test"),
                entry("image", ""),
                entry("location", "Atlantis"),
                entry("maxGoal", 100),
                entry("type", "MONETARY"),
                entry("urgent", false),
                entry("description", "")
        );

        var res = cupboardController.createNeed(needMap, key);

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

    @Test
    void createNeedConflict() throws IOException, DuplicateKeyException {
        when(mockCupboardService.createNeed("Test", "", "Atlantis", 100, Need.GoalType.MONETARY, false, "")).thenThrow(new DuplicateKeyException(""));

        Map<String, Object> needMap = Map.ofEntries(
                entry("name", "Test"),
                entry("image", ""),
                entry("location", "Atlantis"),
                entry("maxGoal", 100),
                entry("type", "MONETARY"),
                entry("urgent", false),
                entry("description", "")
        );

        var res = cupboardController.createNeed(needMap, key);

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

    @Test
    void createNeedUnauthorized() throws IOException, IllegalAccessException {
        doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToCupboard(key);

        Map<String, Object> needMap = Map.ofEntries(
                entry("name", "Test"),
                entry("location", "Atlantis"),
                entry("maxGoal", 100),
                entry("type", "MONETARY"),
                entry("urgent", false)
        );

        var res = cupboardController.createNeed(needMap, key);

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

    @Test
    void getNeeds() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.getNeeds()).thenReturn(new Need[]{need});

        var res = cupboardController.getNeeds();

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

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

        var res = cupboardController.getNeeds();

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

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

        var res = cupboardController.getNeeds();

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

    @Test
    void searchNeeds() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.searchNeeds("Na")).thenReturn(new Need[]{need});

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

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

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

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

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

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

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

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

    @Test
    void getNeed() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.getNeed(need.getId())).thenReturn(need);

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

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

    @Test
    void getNeedIOException() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.getNeed(need.getId())).thenThrow(new IOException());

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

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

    @Test
    void getNeedFail() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.getNeed(need.getId())).thenReturn(null);

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

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

    @Test
    void updateNeeds() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.updateNeed(need, 1)).thenReturn(need);

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

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

    @Test
    void updateNeedsIOException() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.updateNeed(need, 1)).thenThrow(new IOException());

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

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

    @Test
    void updateNeedMissing() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.updateNeed(need, 1)).thenReturn(null);

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

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

    @Test
    void updateNeedBadRequest() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.updateNeed(need, 1)).thenThrow(new IllegalArgumentException());

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

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

    @Test
    void updateNeedUnauthorized() throws IOException, IllegalAccessException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToCupboard(key);

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

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

    @Test
    void deleteNeed() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.getNeed(1)).thenReturn(need);
        when(mockCupboardService.deleteNeed(1)).thenReturn(true);

        var res = cupboardController.deleteNeed(1, key);

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

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

        var res = cupboardController.deleteNeed(1, key);

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

    @Test
    void deleteNeedUnauthorized() throws IOException, IllegalAccessException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.getNeed(1)).thenReturn(need);
        doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToCupboard(key);

        var res = cupboardController.deleteNeed(1, key);

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

    @Test
    void deleteNeedIOException() throws IOException {
        String name = "Test";
        String location = "Atlantis";
        int maxGoal = 100;
        GoalType type = Need.GoalType.MONETARY;
        boolean urgent = false;
        String image = "";
        String description = "";
        var need = new Need(name, location, image, maxGoal, type, urgent, description);
        when(mockCupboardService.getNeed(1)).thenReturn(need);
        when(mockCupboardService.deleteNeed(1)).thenThrow(new IOException());

        var res = cupboardController.deleteNeed(1, key);

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

    @Test
    void checkoutNeeds() throws IOException, IllegalAccessException {
        when(mockCupboardService.getNeed(0)).thenReturn(new Need("name", "image", "location", 0, 10, GoalType.MONETARY, true, "a"));
        doNothing().when(mockCupboardService).checkoutNeed(0, 20, key);

        var needMap = List.of(
                Map.ofEntries(
                        entry("needID", 0),
                        entry("quantity", 20)
                )
        );

        var res = cupboardController.checkoutNeeds(needMap, key);

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

    @Test
    void checkoutNeedsBadRequest() throws IOException, IllegalAccessException {
        doThrow(new IllegalArgumentException()).when(mockCupboardService).checkoutNeed(0, 20, key);

        var needMap = List.of(
                Map.ofEntries(
                        entry("needID", 0),
                        entry("quantity", 20)
                ),
                Map.ofEntries(
                        entry("needID", 2),
                        entry("quantity", 30)
                )
        );

        var res = cupboardController.checkoutNeeds(needMap, key);

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

    @Test
    void checkoutNeedsUnauthorized() throws IOException, IllegalAccessException {
        doThrow(new IllegalAccessException()).when(mockAuthService).keyIsValid(key);

        var needMap = List.of(
                Map.ofEntries(
                        entry("needID", 0),
                        entry("quantity", 20)
                ),
                Map.ofEntries(
                        entry("needID", 2),
                        entry("quantity", 30)
                )
        );

        var res = cupboardController.checkoutNeeds(needMap, key);

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

    @Test
    void checkoutNeedsInternalError() throws IOException, IllegalAccessException {
        when(mockCupboardService.getNeed(0)).thenReturn(new Need("name", "image", "location", 0, 10, GoalType.MONETARY, true, "a"));
        doThrow(new IOException()).when(mockCupboardService).checkoutNeed(0, 20, key);

        var needMap = List.of(
                Map.ofEntries(
                        entry("needID", 0),
                        entry("quantity", 20)
                )
        );

        var res = cupboardController.checkoutNeeds(needMap, key);

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