package ec.edu.espe.movilidad.MovilidadWS.Service;

import ec.edu.espe.movilidad.MovilidadWS.Dao.DaoUzyTPerfil;
import ec.edu.espe.movilidad.MovilidadWS.Dao.DaoUzyTUsuario;
import ec.edu.espe.movilidad.MovilidadWS.Dto.DtoUzyTUsuario;
import ec.edu.espe.movilidad.MovilidadWS.Exceptions.ResourceNotFoundException;
import ec.edu.espe.movilidad.MovilidadWS.Mapper.Components_Class.UzyTPerfilMapper;
import ec.edu.espe.movilidad.MovilidadWS.Mapper.Components_Class.UzyTUsuarioMapper;
import ec.edu.espe.movilidad.MovilidadWS.Model.ModelUzyTPerfil;
import ec.edu.espe.movilidad.MovilidadWS.Model.ModelUzyTUsuario;
import ec.edu.espe.movilidad.MovilidadWS.Service.UzyTUsuario.ServiceUzyTUsuario;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.util.*;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

@ExtendWith(MockitoExtension.class)
class ServiceUzyTUsuarioTests {

    @Mock
    private DaoUzyTUsuario daoUzyTUsuario;

    @Mock
    private DaoUzyTPerfil daoUzyTPerfil;

    @Mock
    private UzyTUsuarioMapper mapper;

    @Mock
    private UzyTPerfilMapper mapperPerfil;

    private ServiceUzyTUsuario serviceUzyTUsuario;

    @BeforeEach
    void setUp() {
        serviceUzyTUsuario = new ServiceUzyTUsuario(daoUzyTUsuario, daoUzyTPerfil, mapper, mapperPerfil);
    }

    //LISTAR USUARIO POR ID
    @DisplayName("Test para listar a los usuarios por el ID")
    @Test
    void testListarPorID() {
        Long id = 1L;
        ModelUzyTUsuario usuario = new ModelUzyTUsuario();
        DtoUzyTUsuario dtoUsuario = new DtoUzyTUsuario();
        dtoUsuario.setUzytusuario_id(id);

        when(daoUzyTUsuario.findById(id)).thenReturn(Optional.of(usuario));
        when(mapper.entityToDto(usuario)).thenReturn(dtoUsuario);

        DtoUzyTUsuario result = serviceUzyTUsuario.ListarPorID(id);
        assertEquals(id, result.getUzytusuario_id());
    }


    @DisplayName("Test para listar a los usuarios por el ID (Caso de error: ID negativo o nulo)")
    @Test
    void testListarPorID_CasoError_IDNegativoONulo() {
        assertThrows(IllegalArgumentException.class, () -> serviceUzyTUsuario.ListarPorID(0L));
        assertThrows(IllegalArgumentException.class, () -> serviceUzyTUsuario.ListarPorID(-1L));
    }

    @DisplayName("Test para listar a los usuarios por el ID (Caso de error: usuario no encontrado)")
    @Test
    void testListarPorID_CasoError_UsuarioNoEncontrado() {
        Long id = 1L;
        when(daoUzyTUsuario.findById(id)).thenReturn(Optional.empty()); // se devulve vacío
        assertThrows(ResourceNotFoundException.class, () -> serviceUzyTUsuario.ListarPorID(id));
    }

    //LISTAR REGISTROS

    @DisplayName("Test para listar todos los usuarios existentes (Caso exitoso)")
    @Test
    void testListarRegistros_CasoExitoso() {

        List<ModelUzyTUsuario> usuarios = Arrays.asList(new ModelUzyTUsuario(), new ModelUzyTUsuario());
        DtoUzyTUsuario dtoUsuario = new DtoUzyTUsuario();

        when(daoUzyTUsuario.findAll()).thenReturn(usuarios);
        when(mapper.entitiesToDtos(usuarios)).thenReturn(Arrays.asList(dtoUsuario, dtoUsuario));

        List<DtoUzyTUsuario> result = serviceUzyTUsuario.ListarRegistros();

        assertEquals(2, result.size());
        assertEquals(dtoUsuario, result.get(0));
        assertEquals(dtoUsuario, result.get(1));
    }

    @DisplayName("Test para listar usuarios registrados - Sin resultados")
    @Test
    void testListarRegistros_SinResultados() {
        List<ModelUzyTUsuario> usuarios = Collections.emptyList();

        when(daoUzyTUsuario.findAll()).thenReturn(usuarios);

        List<DtoUzyTUsuario> result = serviceUzyTUsuario.ListarRegistros();

        assertTrue(result.isEmpty());
    }

    @DisplayName("Test para listar usuarios registrados - Excepción en el servicio")
    @Test
    void testListarRegistros_ExcepcionEnServicio() {
        when(daoUzyTUsuario.findAll()).thenThrow(new RuntimeException("Error en el servicio"));

        assertThrows(RuntimeException.class, () -> serviceUzyTUsuario.ListarRegistros());
    }


    //BUSCAR USUARIOS

    @DisplayName("Test para buscar usuarios por nombre (Caso exitoso)")
    @Test
    void testFindByUsuario_CasoExitoso() {
        String usuario = "Joel";
        ModelUzyTUsuario modelUzyTUsuario = new ModelUzyTUsuario();
        DtoUzyTUsuario dtoUzyTUsuario = new DtoUzyTUsuario();

        when(daoUzyTUsuario.findByUsuario(usuario)).thenReturn(Collections.singletonList(modelUzyTUsuario));
        when(mapper.entitiesToDtos(Collections.singletonList(modelUzyTUsuario))).thenReturn(Collections.singletonList(dtoUzyTUsuario));

        List<DtoUzyTUsuario> result = serviceUzyTUsuario.findByUsuario(usuario);

        assertEquals(1, result.size());
        assertEquals(dtoUzyTUsuario, result.get(0));
    }

    @DisplayName("Test para buscar usuarios por nombre (Caso de error: nombre de usuario nulo o vacío)")
    @Test
    void testFindByUsuario_CasoError_NombreNuloOVacio() {
        assertThrows(IllegalArgumentException.class, () -> serviceUzyTUsuario.findByUsuario(null));
        assertThrows(IllegalArgumentException.class, () -> serviceUzyTUsuario.findByUsuario(""));
    }

    @DisplayName("Test para buscar usuarios por nombre (Caso de error: usuarios no encontrados)")
    @Test
    void testFindByUsuario_CasoError_UsuariosNoEncontrados() {
        String usuario = "example";

        when(daoUzyTUsuario.findByUsuario(usuario)).thenReturn(Collections.emptyList());

        assertThrows(ResourceNotFoundException.class, () -> serviceUzyTUsuario.findByUsuario(usuario));
    }

    //GUARDAR USUARIO

    @DisplayName("Test para guardar un nuevo usuario correctamente (Caso exitoso)")
    @Test
    void testGuardar_CasoExitoso() throws Exception {
        // Datos del usuario a simular
        DtoUzyTUsuario dtoUzyTUsuario = new DtoUzyTUsuario();
        dtoUzyTUsuario.setUzytusuario_nombres("NombreUsuario");
        dtoUzyTUsuario.setUzytusuario_apellidos("ApellidosUsuario");
        dtoUzyTUsuario.setUzytusuario_email("usuario@example.com");

        // Convertir el objeto DtoUzyTUsuario a ModelUzyTUsuario
        ModelUzyTUsuario modelUzyTUsuario = new ModelUzyTUsuario();
        modelUzyTUsuario.setUzytusuario_nombres(dtoUzyTUsuario.getUzytusuario_nombres());
        modelUzyTUsuario.setUzytusuario_apellidos(dtoUzyTUsuario.getUzytusuario_apellidos());
        modelUzyTUsuario.setUzytusuario_email(dtoUzyTUsuario.getUzytusuario_email());

        // Configurar el comportamiento esperado del mapper y el daoUzyTUsuario
        when(mapper.dtoToEntity(dtoUzyTUsuario)).thenReturn(modelUzyTUsuario);
        when(daoUzyTUsuario.save(modelUzyTUsuario)).thenReturn(modelUzyTUsuario);
        when(mapper.entityToDto(modelUzyTUsuario)).thenReturn(dtoUzyTUsuario);

        // Ejecutar el método a probar
        DtoUzyTUsuario result = serviceUzyTUsuario.guardar(dtoUzyTUsuario);

        // Verificar el resultado
        assertNotNull(result);
        assertEquals(dtoUzyTUsuario, result);
    }

    @DisplayName("Test para guardar un nuevo usuario - Apellido nulo (Caso de error)")
    @Test
    void testGuardar_ApellidoNulo() {
        // Datos del usuario a simular con apellido nulo
        DtoUzyTUsuario dtoUzyTUsuario = new DtoUzyTUsuario();
        dtoUzyTUsuario.setUzytusuario_nombres("NombreUsuario");
        dtoUzyTUsuario.setUzytusuario_apellidos(null);
        dtoUzyTUsuario.setUzytusuario_email("usuario@example.com");

        // Ejecutar el método a probar y verificar que se lance la excepción
        assertThrows(IllegalArgumentException.class, () -> serviceUzyTUsuario.guardar(dtoUzyTUsuario));
    }

    @DisplayName("Test para guardar un nuevo usuario - Excepción en el servicio (Caso de error)")
    @Test
    void testGuardar_ExcepcionEnServicio()  {
        // Datos del usuario a simular
        DtoUzyTUsuario dtoUzyTUsuario = new DtoUzyTUsuario();
        dtoUzyTUsuario.setUzytusuario_nombres("NombreUsuario");
        dtoUzyTUsuario.setUzytusuario_apellidos("ApellidosUsuario");
        dtoUzyTUsuario.setUzytusuario_email("usuario@example.com");

        // Convertir el objeto DtoUzyTUsuario a ModelUzyTUsuario
        ModelUzyTUsuario modelUzyTUsuario = new ModelUzyTUsuario();
        modelUzyTUsuario.setUzytusuario_nombres(dtoUzyTUsuario.getUzytusuario_nombres());
        modelUzyTUsuario.setUzytusuario_apellidos(dtoUzyTUsuario.getUzytusuario_apellidos());
        modelUzyTUsuario.setUzytusuario_email(dtoUzyTUsuario.getUzytusuario_email());

        // Configurar el comportamiento esperado del mapper y el daoUzyTUsuario
        when(mapper.dtoToEntity(dtoUzyTUsuario)).thenReturn(modelUzyTUsuario);
        when(daoUzyTUsuario.save(modelUzyTUsuario)).thenThrow(new RuntimeException("Error en el servicio"));

        // Ejecutar el método a probar y verificar que se lance la excepción
        assertThrows(Exception.class, () -> serviceUzyTUsuario.guardar(dtoUzyTUsuario));
    }


    @DisplayName("Test para editar un usuario inexistente (Caso de error)")
    @Test
    void testEditar_UsuarioNoExiste() {
        // Datos del usuario a simular
        Long id = 1L;
        DtoUzyTUsuario dtoUzyTUsuario = new DtoUzyTUsuario();
        dtoUzyTUsuario.setUzytusuario_nombres("NuevoNombre");

        // Ejecutar el método a probar y verificar que se lance la excepción
        assertThrows(ResourceNotFoundException.class, () -> serviceUzyTUsuario.editar(id, dtoUzyTUsuario));
    }


    //EDITAR USUARIO
    @DisplayName("Test para editar un usuario - ID negativo (Caso de error)")
    @Test
    void testEditar_IdNegativo() {
        // Datos del usuario a simular con ID negativo
        Long id = -1L;
        DtoUzyTUsuario dtoUzyTUsuario = new DtoUzyTUsuario();
        dtoUzyTUsuario.setUzytusuario_nombres("NuevoNombre");

        // Ejecutar el método a probar y verificar que se lance la excepción
        assertThrows(IllegalArgumentException.class, () -> serviceUzyTUsuario.editar(id, dtoUzyTUsuario));
    }

    @DisplayName("Test para actualizar un empleado")
    @Test
    void testActualizarEmpleado() {
        // Datos del usuario a simular
        Long id = 1L;
        DtoUzyTUsuario dtoUzyTUsuario = new DtoUzyTUsuario();
        dtoUzyTUsuario.setUzytusuario_id(id);
        dtoUzyTUsuario.setUzytusuario_nombres("Nuevo Nombre");
        dtoUzyTUsuario.setUzytusuario_apellidos("Nuevo Apellido");

        // Obtener el usuario existente a través del ID
        ModelUzyTUsuario usuarioExistente = new ModelUzyTUsuario();
        usuarioExistente.setUzytusuario_id(id);
        usuarioExistente.setUzytusuario_nombres("Nombre U");
        usuarioExistente.setUzytusuario_apellidos("Apellido U");

        // Configurar el comportamiento esperado del método findById del daoUzyTUsuario
        when(daoUzyTUsuario.findById(id)).thenReturn(Optional.of(usuarioExistente));
        // Configurar el comportamiento esperado del método existsById del daoUzyTUsuario
        when(daoUzyTUsuario.existsById(id)).thenReturn(true);
        // Configurar el comportamiento esperado del método save del daoUzyTUsuario
        when(daoUzyTUsuario.save(any())).thenReturn(usuarioExistente);

        // Configurar el comportamiento esperado del método entityToDto del mapper
        when(mapper.entityToDto(any())).thenReturn(dtoUzyTUsuario);

        // Ejecutar el método a probar
        DtoUzyTUsuario result = serviceUzyTUsuario.editar(id, dtoUzyTUsuario);

        // Verificar el resultado
        assertNotNull(result);
        assertEquals(dtoUzyTUsuario.getUzytusuario_id(), result.getUzytusuario_id());
        assertEquals(dtoUzyTUsuario.getUzytusuario_nombres(), result.getUzytusuario_nombres());
        assertEquals(dtoUzyTUsuario.getUzytusuario_apellidos(), result.getUzytusuario_apellidos());

        // Verificar que el método findById del daoUzyTUsuario fue llamado con el ID correcto
        verify(daoUzyTUsuario).findById(id);
        // Verificar que el método existsById del daoUzyTUsuario fue llamado con el ID correcto
        verify(daoUzyTUsuario).existsById(id);
        // Verificar que el método save del daoUzyTUsuario fue llamado con el usuario actualizado
        verify(daoUzyTUsuario).save(any());
        // Verificar que el método entityToDto del mapper fue llamado con el usuario actualizado
        verify(mapper).entityToDto(usuarioExistente);
    }

    //ELIMINAR USUARIO
    @Test
    void testEliminar_CasoExitoso() {
        // Datos del usuario a simular
        Long id = 1L;
        ModelUzyTUsuario usuarioExistente = new ModelUzyTUsuario();
        usuarioExistente.setUzytusuario_id(id);

        // Configurar el comportamiento del daoUzyTUsuario para findById y delete
        when(daoUzyTUsuario.findById(id)).thenReturn(Optional.of(usuarioExistente));
        doNothing().when(daoUzyTUsuario).delete(usuarioExistente);

        // Ejecutar el método a probar
        boolean result = serviceUzyTUsuario.eliminar(id);

        // Verificar que el usuario fue eliminado
        assertTrue(result);
        // Verificar que el método findById fue llamado
        verify(daoUzyTUsuario, times(1)).findById(id);
        // Verificar que el método delete fue llamado
        verify(daoUzyTUsuario, times(1)).delete(usuarioExistente);
    }


    @Test
    void testEliminar_IDNuloOMenorOIgualACero() {
        // Datos del usuario a simular
        Long id = null;

        // Ejecutar el método a probar y verificar que se lance la excepción
        assertThrows(IllegalArgumentException.class, () -> serviceUzyTUsuario.eliminar(id));
    }


    @Test
    void testEliminar_UsuarioNoExiste() {
        // Datos del usuario a simular
        Long id = 1L;

        // Configurar el comportamiento del daoUzyTUsuario para findById
        when(daoUzyTUsuario.findById(id)).thenReturn(Optional.empty());

        // Ejecutar el método a probar y verificar que se lanza una ResourceNotFoundException
        assertThrows(ResourceNotFoundException.class, () -> serviceUzyTUsuario.eliminar(id));

        // Verificar que el método findById fue llamado
        verify(daoUzyTUsuario, times(1)).findById(id);
    }

    //ASIGNAR PERFIL USUARIO

    @Test
    void testAsignarPerfilAUsuario_PerfilYaAsignado() {

        Long uzytusuario_id = 1L;
        Long uzytperfil_id = 2L;
        ModelUzyTUsuario usuario = new ModelUzyTUsuario();
        usuario.setUzytusuario_id(uzytusuario_id);
        usuario.setUzytusuario_nombres("Usuario de Prueba");

        ModelUzyTPerfil perfil = new ModelUzyTPerfil();
        perfil.setUzytperfil_id(uzytperfil_id);
        perfil.setUzytperfil_nombre("Perfil de Prueba");

        usuario.getUzytperfils().add(perfil);

        when(daoUzyTUsuario.findById(uzytusuario_id)).thenReturn(Optional.of(usuario));
        when(daoUzyTPerfil.findById(uzytperfil_id)).thenReturn(Optional.of(perfil));

        // Se ejecuta el método a probar y verifica que se lanza la excepción
        Assertions.assertThrows(IllegalArgumentException.class, () -> serviceUzyTUsuario.asignarPerfilAUsuario(uzytusuario_id, uzytperfil_id));

        //  Verifica que el perfil no se agregó nuevamente al usuario
        Assertions.assertEquals(1, usuario.getUzytperfils().size());
    }

    @DisplayName("Test para asignar un perfil a un usuario - Usuario o Perfil no existen (Caso de error)")
    @Test
    void testAsignarPerfilAUsuario_UsuarioOPerfilNoExiste() {
        // Datos de usuario y perfil a simular (ID inválidos)
        Long usuarioId = 1L;
        Long perfilId = 1L;

        // Configurar el comportamiento del daoUzyTUsuario y daoUzyTPerfil para findById (usar lenient)
        lenient().when(daoUzyTUsuario.findById(usuarioId)).thenReturn(Optional.empty());
        lenient().when(daoUzyTPerfil.findById(perfilId)).thenReturn(Optional.empty());

        // Ejecutar el método a probar y verificar que se lance la excepción
        assertThrows(ResourceNotFoundException.class, () -> serviceUzyTUsuario.asignarPerfilAUsuario(usuarioId, perfilId));
    }


    @DisplayName("Test para asignar un perfil a un usuario - ID negativo (Caso de error)")
    @Test
    void testAsignarPerfilAUsuario_IdNegativo() {
        // Datos de usuario y perfil a simular (IDs negativos)
        Long usuarioId = -1L;
        Long perfilId = -1L;

        // Ejecutar el método a probar y verificar que se lance la excepción
        assertThrows(IllegalArgumentException.class, () -> serviceUzyTUsuario.asignarPerfilAUsuario(usuarioId, perfilId));
    }
}