package dev.lions.user.manager.resource;

import dev.lions.user.manager.api.RoleResourceApi;
import dev.lions.user.manager.dto.common.ApiErrorDTO;
import dev.lions.user.manager.dto.role.RoleAssignmentDTO;
import dev.lions.user.manager.dto.role.RoleAssignmentRequestDTO;
import dev.lions.user.manager.dto.role.RoleDTO;
import dev.lions.user.manager.enums.role.TypeRole;
import dev.lions.user.manager.service.RoleService;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * REST Resource pour la gestion des rôles Keycloak
 * Implémente l'interface API commune.
 * Annotation explicite des méthodes pour éviter les problèmes d'héritage JAX-RS
 * dans Quarkus.
 */
@Slf4j
@jakarta.enterprise.context.ApplicationScoped
@Path("/api/roles")
public class RoleResource implements RoleResourceApi {

    @Inject
    RoleService roleService;

    // ==================== Endpoints Realm Roles ====================

    @Override
    @POST
    @Path("/realm")
    @RolesAllowed({ "admin", "role_manager" })
    public Response createRealmRole(
            @Valid @NotNull RoleDTO roleDTO,
            @QueryParam("realm") String realmName) {
        log.info("POST /api/roles/realm - Création du rôle realm: {} dans le realm: {}",
                roleDTO.getName(), realmName);

        try {
            RoleDTO createdRole = roleService.createRealmRole(roleDTO, realmName);
            return Response.status(Response.Status.CREATED).entity(createdRole).build();
        } catch (IllegalArgumentException e) {
            log.warn("Données invalides lors de la création du rôle: {}", e.getMessage());
            return Response.status(Response.Status.CONFLICT)
                    .entity(new ApiErrorDTO(e.getMessage()))
                    .build();
        } catch (Exception e) {
            log.error("Erreur lors de la création du rôle realm", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    @GET
    @Path("/realm/{roleName}")
    @RolesAllowed({ "admin", "role_manager", "role_viewer" })
    public RoleDTO getRealmRole(@PathParam("roleName") String roleName, @QueryParam("realm") String realmName) {
        log.info("GET /api/roles/realm/{} - realm: {}", roleName, realmName);
        return roleService.getRoleByName(roleName, realmName, TypeRole.REALM_ROLE, null)
                .orElseThrow(() -> new RuntimeException("Rôle non trouvé"));
    }

    @Override
    @GET
    @Path("/realm")
    @RolesAllowed({ "admin", "role_manager", "role_viewer" })
    public List<RoleDTO> getAllRealmRoles(@QueryParam("realm") String realmName) {
        log.info("GET /api/roles/realm - realm: {}", realmName);
        return roleService.getAllRealmRoles(realmName);
    }

    @Override
    @PUT
    @Path("/realm/{roleName}")
    @RolesAllowed({ "admin", "role_manager" })
    public RoleDTO updateRealmRole(@PathParam("roleName") String roleName, @Valid @NotNull RoleDTO roleDTO,
            @QueryParam("realm") String realmName) {
        log.info("PUT /api/roles/realm/{} - realm: {}", roleName, realmName);

        Optional<RoleDTO> existingRole = roleService.getRoleByName(roleName, realmName, TypeRole.REALM_ROLE, null);
        if (existingRole.isEmpty()) {
            throw new RuntimeException("Rôle non trouvé");
        }

        return roleService.updateRole(existingRole.get().getId(), roleDTO, realmName, TypeRole.REALM_ROLE, null);
    }

    @Override
    @DELETE
    @Path("/realm/{roleName}")
    @RolesAllowed({ "admin" })
    public void deleteRealmRole(@PathParam("roleName") String roleName, @QueryParam("realm") String realmName) {
        log.info("DELETE /api/roles/realm/{} - realm: {}", roleName, realmName);

        Optional<RoleDTO> existingRole = roleService.getRoleByName(roleName, realmName, TypeRole.REALM_ROLE, null);
        if (existingRole.isEmpty()) {
            throw new RuntimeException("Rôle non trouvé");
        }

        roleService.deleteRole(existingRole.get().getId(), realmName, TypeRole.REALM_ROLE, null);
    }

    // ==================== Endpoints Client Roles ====================

    @Override
    @POST
    @Path("/client/{clientId}")
    @RolesAllowed({ "admin", "role_manager" })
    public Response createClientRole(@PathParam("clientId") String clientId, @Valid @NotNull RoleDTO roleDTO,
            @QueryParam("realm") String realmName) {
        log.info("POST /api/roles/client/{} - Création du rôle client dans le realm: {}",
                clientId, realmName);

        try {
            RoleDTO createdRole = roleService.createClientRole(roleDTO, clientId, realmName);
            return Response.status(Response.Status.CREATED).entity(createdRole).build();
        } catch (IllegalArgumentException e) {
            log.warn("Données invalides lors de la création du rôle client: {}", e.getMessage());
            return Response.status(Response.Status.CONFLICT)
                    .entity(new ApiErrorDTO(e.getMessage()))
                    .build();
        } catch (Exception e) {
            log.error("Erreur lors de la création du rôle client", e);
            throw new RuntimeException(e);
        }
    }

    @Override
    @GET
    @Path("/client/{clientId}/{roleName}")
    @RolesAllowed({ "admin", "role_manager", "role_viewer" })
    public RoleDTO getClientRole(@PathParam("clientId") String clientId, @PathParam("roleName") String roleName,
            @QueryParam("realm") String realmName) {
        log.info("GET /api/roles/client/{}/{} - realm: {}", clientId, roleName, realmName);
        return roleService.getRoleByName(roleName, realmName, TypeRole.CLIENT_ROLE, clientId)
                .orElseThrow(() -> new RuntimeException("Rôle client non trouvé"));
    }

    @Override
    @GET
    @Path("/client/{clientId}")
    @RolesAllowed({ "admin", "role_manager", "role_viewer" })
    public List<RoleDTO> getAllClientRoles(@PathParam("clientId") String clientId,
            @QueryParam("realm") String realmName) {
        log.info("GET /api/roles/client/{} - realm: {}", clientId, realmName);
        return roleService.getAllClientRoles(realmName, clientId);
    }

    @Override
    @DELETE
    @Path("/client/{clientId}/{roleName}")
    @RolesAllowed({ "admin" })
    public void deleteClientRole(@PathParam("clientId") String clientId, @PathParam("roleName") String roleName,
            @QueryParam("realm") String realmName) {
        log.info("DELETE /api/roles/client/{}/{} - realm: {}", clientId, roleName, realmName);

        Optional<RoleDTO> existingRole = roleService.getRoleByName(roleName, realmName, TypeRole.CLIENT_ROLE, clientId);
        if (existingRole.isEmpty()) {
            throw new RuntimeException("Rôle client non trouvé");
        }

        roleService.deleteRole(existingRole.get().getId(), realmName, TypeRole.CLIENT_ROLE, clientId);
    }

    // ==================== Endpoints Attribution de rôles ====================

    @Override
    @POST
    @Path("/assign/realm/{userId}")
    @RolesAllowed({ "admin", "role_manager" })
    public void assignRealmRoles(@PathParam("userId") String userId, @QueryParam("realm") String realmName,
            @NotNull RoleAssignmentRequestDTO request) {
        log.info("POST /api/roles/assign/realm/{} - Attribution de {} rôles", userId, request.getRoleNames().size());

        RoleAssignmentDTO assignment = RoleAssignmentDTO.builder()
                .userId(userId)
                .roleNames(request.getRoleNames())
                .typeRole(TypeRole.REALM_ROLE)
                .realmName(realmName)
                .build();
        roleService.assignRolesToUser(assignment);
    }

    @Override
    @POST
    @Path("/revoke/realm/{userId}")
    @RolesAllowed({ "admin", "role_manager" })
    public void revokeRealmRoles(@PathParam("userId") String userId, @QueryParam("realm") String realmName,
            @NotNull RoleAssignmentRequestDTO request) {
        log.info("POST /api/roles/revoke/realm/{} - Révocation de {} rôles", userId, request.getRoleNames().size());

        RoleAssignmentDTO assignment = RoleAssignmentDTO.builder()
                .userId(userId)
                .roleNames(request.getRoleNames())
                .typeRole(TypeRole.REALM_ROLE)
                .realmName(realmName)
                .build();
        roleService.revokeRolesFromUser(assignment);
    }

    @Override
    @POST
    @Path("/assign/client/{clientId}/{userId}")
    @RolesAllowed({ "admin", "role_manager" })
    public void assignClientRoles(@PathParam("clientId") String clientId, @PathParam("userId") String userId,
            @QueryParam("realm") String realmName,
            @NotNull RoleAssignmentRequestDTO request) {
        log.info("POST /api/roles/assign/client/{}/{} - Attribution de {} rôles client",
                clientId, userId, request.getRoleNames().size());

        RoleAssignmentDTO assignment = RoleAssignmentDTO.builder()
                .userId(userId)
                .roleNames(request.getRoleNames())
                .typeRole(TypeRole.CLIENT_ROLE)
                .realmName(realmName)
                .clientName(clientId)
                .build();
        roleService.assignRolesToUser(assignment);
    }

    @Override
    @GET
    @Path("/user/realm/{userId}")
    @RolesAllowed({ "admin", "role_manager", "role_viewer" })
    public List<RoleDTO> getUserRealmRoles(@PathParam("userId") String userId, @QueryParam("realm") String realmName) {
        log.info("GET /api/roles/user/realm/{} - realm: {}", userId, realmName);
        return roleService.getUserRealmRoles(userId, realmName);
    }

    @Override
    @GET
    @Path("/user/client/{clientId}/{userId}")
    @RolesAllowed({ "admin", "role_manager", "role_viewer" })
    public List<RoleDTO> getUserClientRoles(@PathParam("clientId") String clientId, @PathParam("userId") String userId,
            @QueryParam("realm") String realmName) {
        log.info("GET /api/roles/user/client/{}/{} - realm: {}", clientId, userId, realmName);
        return roleService.getUserClientRoles(userId, clientId, realmName);
    }

    // ==================== Endpoints Rôles composites ====================

    @Override
    @POST
    @Path("/composite/{roleName}/add")
    @RolesAllowed({ "admin", "role_manager" })
    public void addComposites(@PathParam("roleName") String roleName, @QueryParam("realm") String realmName,
            @NotNull RoleAssignmentRequestDTO request) {
        log.info("POST /api/roles/composite/{}/add - Ajout de {} composites", roleName, request.getRoleNames().size());

        Optional<RoleDTO> parentRole = roleService.getRoleByName(roleName, realmName, TypeRole.REALM_ROLE, null);
        if (parentRole.isEmpty()) {
            throw new RuntimeException("Rôle parent non trouvé");
        }

        List<String> childRoleIds = request.getRoleNames().stream()
                .map(name -> {
                    Optional<RoleDTO> role = roleService.getRoleByName(name, realmName, TypeRole.REALM_ROLE, null);
                    return role.map(RoleDTO::getId).orElse(null);
                })
                .filter(id -> id != null)
                .collect(Collectors.toList());

        roleService.addCompositeRoles(parentRole.get().getId(), childRoleIds, realmName, TypeRole.REALM_ROLE, null);
    }

    @Override
    @GET
    @Path("/composite/{roleName}")
    @RolesAllowed({ "admin", "role_manager", "role_viewer" })
    public List<RoleDTO> getComposites(@PathParam("roleName") String roleName, @QueryParam("realm") String realmName) {
        log.info("GET /api/roles/composite/{} - realm: {}", roleName, realmName);

        Optional<RoleDTO> role = roleService.getRoleByName(roleName, realmName, TypeRole.REALM_ROLE, null);
        if (role.isEmpty()) {
            throw new RuntimeException("Rôle non trouvé");
        }

        return roleService.getCompositeRoles(role.get().getId(), realmName, TypeRole.REALM_ROLE, null);
    }
}
