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.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

import com.ufund.api.ufundapi.DuplicateKeyException;
import com.ufund.api.ufundapi.model.User;
import com.ufund.api.ufundapi.model.UserAuth;
import com.ufund.api.ufundapi.service.AuthService;
import com.ufund.api.ufundapi.service.UserService;

@Tag("Controller-tier")
public class UserControllerTest {
    private UserController userController;
    private AuthService mockAuthService;
    private UserService mockUserService;
    private Map<String, String> userMap;

    @BeforeEach
    public void setupUserController() {
        mockUserService = mock(UserService.class);
        mockAuthService = mock(AuthService.class);
        userController = new UserController(mockUserService, mockAuthService);
        userMap = Map.ofEntries(
                entry("username", "Test"),
                entry("password", "Pass")
        );
    }

    @Test
    public void testGetUser() throws IOException { // getUser may throw IOException
        // Setup
        String username = "Test";
        User user = User.create(username, "pass");
        String key = UserAuth.generate(username).getKey(    );
        // When the same id is passed in, our mock User DAO will return the User object
        when(mockUserService.getUser(username)).thenReturn(user);
        

        // Invoke
        ResponseEntity<Object> response = userController.getUser(username, key);

        // Analyze
        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertNotNull(response.getBody());
        assertEquals(user.getUsername(), ((User) response.getBody()).getUsername());
    }

    @Test
    public void testGetUserNotFound() throws Exception { // createUser may throw IOException
        // Setup
        String username = "Test";
        String key = UserAuth.generate(username).getKey();
        // When the same id is passed in, our mock User service will return null, simulating
        // no User found
        when(mockUserService.getUser(username)).thenReturn(null);
        

        // Invoke
        ResponseEntity<Object> response = userController.getUser(username, key);

        // Analyze
        assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
    }

    @Test
    public void testGetUserUnauthorized() throws Exception { // createUser may throw IOException
        // Setup
        String username = "Test";
        String key = UserAuth.generate(username).getKey();
        // When getUser is called on the Mock User service, throw an IOException
        // doThrow(new IllegalAccessException()).when(mockUserService).getUser(username);
        doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key);

        // Invoke
        ResponseEntity<Object> response = userController.getUser(username, key);

        // Analyze
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
    }

    @Test
    public void testGetUserHandleException() throws Exception { // createUser may throw IOException
        // Setup
        String username = "Test";
        String key = UserAuth.generate(username).getKey();
        // When getUser is called on the Mock User service, throw an IOException
        doThrow(new IOException()).when(mockUserService).getUser(username);

        // Invoke
        ResponseEntity<Object> response = userController.getUser(username, key);

        // Analyze
        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
    }

    @Test
    public void testCreateUser() throws IOException, DuplicateKeyException { // createUser may throw IOException
        // Setup
        String username = "Test";
        String password = "Pass";
        User user = User.create(username, "pass");
        // when createUser is called, return true simulating successful
        // creation and save
        when(mockUserService.createUser(username, password)).thenReturn(user);

                

        // Invoke
        ResponseEntity<Object> response = userController.createUser(userMap);

        // Analyze
        assertEquals(HttpStatus.CREATED, response.getStatusCode());
        assertEquals(user, response.getBody());
    }

    @Test
    public void testCreateUserFailed() throws IOException, DuplicateKeyException { // createUser may throw IOException
        // Setup
        String username = "Test";
        String password = "Pass";
        // when createUser is called, return false simulating failed
        // creation and save
        when(mockUserService.createUser(username, password)).thenReturn(null);

        

        // Invoke
        ResponseEntity<Object> response = userController.createUser(userMap);

        // Analyze
        assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
    }

    @Test
    public void testCreateUserDuplicate() throws IOException, DuplicateKeyException { // createUser may throw IOException
        // Setup
        String username = "Test";
        String password = "Pass";
        // when createUser is called, return false simulating failed
        // creation and save
        when(mockUserService.createUser(username, password)).thenThrow(DuplicateKeyException.class);

        // Invoke
        ResponseEntity<Object> response = userController.createUser(userMap);

        // Analyze
        assertEquals(HttpStatus.CONFLICT, response.getStatusCode());
    }

    @Test
    public void testCreateUserHandleException() throws IOException, DuplicateKeyException { // createUser may throw IOException
        // Setup
        String username = "Test";
        String password = "Pass";

        // When createUser is called on the Mock User service, throw an IOException
        doThrow(new IOException()).when(mockUserService).createUser(username, password);

        

        // Invoke
        ResponseEntity<Object> response = userController.createUser(userMap);

        // Analyze
        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
    }

    @Test
    public void testUpdateUser() throws IOException { // updateUser may throw IOException
        // Setup
        String username = "Test";
        User user = User.create(username, "pass");
        String key = UserAuth.generate(username).getKey();
        // when updateUser is called, return true simulating successful
        // update and save
        when(mockUserService.updateUser(user, username)).thenReturn(user);

        // Invoke
        ResponseEntity<Object> response = userController.updateUser(user, username, key);

        // Analyze
        assertEquals(HttpStatus.OK, response.getStatusCode());
        assertEquals(user, response.getBody());
    }

    @Test
    public void testUpdateUserFailed() throws IOException { // updateUser may throw IOException
        // Setup
        String username = "Test";
        User user = User.create(username, "pass");
        String key = UserAuth.generate(username).getKey();
        // when updateUser is called, return true simulating successful
        // update and save
        when(mockUserService.updateUser(user, username)).thenReturn(null);

        // Invoke
        ResponseEntity<Object> response = userController.updateUser(user, username, key);

        // Analyze
        assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
    }

    @Test
    public void testUpdateUserInvalidParameter() throws IOException { // updateUser may throw IOException
        // Setup
        String username = "Test";
        User user = User.create(username, "pass");
        String key = UserAuth.generate(username).getKey();
        // When updateUser is called on the Mock User DAO, throw an IOException
        doThrow(new IOException()).when(mockUserService).updateUser(user, username);

        // Invoke
        ResponseEntity<Object> response = userController.updateUser(user, username, key);

        // Analyze
        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
    }

    @Test
    public void testUpdateUserUnauthorized() throws IOException, IllegalAccessException { // updateUser may throw IOException
        // Setup
        String username = "Test";
        User user = User.create(username, "pass");
        String key = UserAuth.generate(username).getKey();
        // When updateUser is called on the Mock User service, throw a Invalid Parameter exception
        // exception
        doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key);
        

        // Invoke
        ResponseEntity<Object> response = userController.updateUser(user, username, key);

        // Analyze
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
    }

    @Test
    public void testDeleteUser() throws IOException { // deleteUser may throw IOException
        // Setup
        String username = "Test";
        String key = UserAuth.generate(username).getKey();
        // when deleteUser is called return true, simulating successful deletion
        when(mockUserService.deleteUser(username)).thenReturn(true);

        // Invoke
        ResponseEntity<Object> response = userController.deleteUser(username, key);

        // Analyze
        assertEquals(HttpStatus.OK, response.getStatusCode());
    }

    @Test
    public void testDeleteUserNotFound() throws IOException { // deleteUser may throw IOException
        // Setup
        String username = "Test";
        String key = UserAuth.generate(username).getKey();
        // when deleteUser is called return false, simulating failed deletion
        when(mockUserService.deleteUser(username)).thenReturn(false);

        // Invoke
        ResponseEntity<Object> response = userController.deleteUser(username, key);

        // Analyze
        assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode());
    }

    @Test
    public void testDeleteUserHandleException() throws IOException { // deleteUser may throw IOException
        // Setup
        String username = "Test";
        String key = UserAuth.generate(username).getKey();
        // When deleteUser is called on the Mock User service, throw an IOException
        doThrow(new IOException()).when(mockUserService).deleteUser(username);

        // Invoke
        ResponseEntity<Object> response = userController.deleteUser(username, key);

        // Analyze
        assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
    }

    @Test
    public void testDeleteUserUnauthorized() throws IOException, IllegalAccessException { // deleteUser may throw IOException
        // Setup
        String username = "Test";
        String key = UserAuth.generate(username).getKey();
        // When deleteUser is called on the Mock User service, throw an IOException
        doThrow(new IllegalAccessException()).when(mockAuthService).keyHasAccessToUser(username, key);

        // Invoke
        ResponseEntity<Object> response = userController.deleteUser(username, key);

        // Analyze
        assertEquals(HttpStatus.UNAUTHORIZED, response.getStatusCode());
    }

}