package org.github.tess1o.geopulse.user.rest;

import jakarta.annotation.security.RolesAllowed;
import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import lombok.extern.slf4j.Slf4j;
import org.github.tess1o.geopulse.auth.service.CurrentUserService;
import org.github.tess1o.geopulse.auth.exceptions.InvalidPasswordException;
import org.github.tess1o.geopulse.shared.api.ApiResponse;
import org.github.tess1o.geopulse.streaming.service.boat.BoatSetupService;
import org.github.tess1o.geopulse.user.mapper.UserMapper;
import org.github.tess1o.geopulse.user.model.*;
import org.github.tess1o.geopulse.user.service.UserService;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.multipart.FileUpload;

import java.nio.file.Files;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import org.eclipse.microprofile.openapi.annotations.tags.Tag;

/**
 * REST resource for user management.
 */
@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
@RequestScoped
@Slf4j
@Tag(name = "User: Users", description = "Register users and manage the authenticated user profile, avatar, password, and preferences.")
public class UserResource {

    private final UserService userService;
    private final UserMapper userMapper;
    private final CurrentUserService currentUserService;
    private final BoatSetupService boatSetupService;

    @Inject
    public UserResource(UserService userService,
                        UserMapper userMapper,
                        CurrentUserService currentUserService,
                        BoatSetupService boatSetupService) {
        this.userService = userService;
        this.userMapper = userMapper;
        this.currentUserService = currentUserService;
        this.boatSetupService = boatSetupService;
    }

    /**
     * Register a new user.
     *
     * @param request The user registration request
     * @return The created user
     */

    @POST
    @Path("/register")
    public Response registerUser(@Valid UserRegistrationRequest request) {
        try {
            UserEntity user = userService.registerUser(
                    request.getEmail(),
                    request.getPassword(),
                    request.getFullName(),
                    request.getTimezone()
            );
            UserResponse response = userMapper.toResponse(user);
            return Response.status(Response.Status.CREATED).entity(ApiResponse.success(response)).build();
        } catch (IllegalArgumentException e) {
            log.error("Failed to register user due to duplicate", e);
            return Response.status(Response.Status.CONFLICT)
                    .entity(ApiResponse.error(e.getMessage()))
                    .build();
        } catch (Exception e) {
            log.error("Failed to register user", e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity(ApiResponse.error("Failed to register user"))
                    .build();
        }
    }


    @POST
    @Path("/update")
    @RolesAllowed({"USER", "ADMIN"})
    public Response updateProfile(@Valid UpdateProfileRequest request) {
        try {
            UUID userId = currentUserService.getCurrentUserId();
            log.info("Updating profile with {}", request);
            UserEntity updatedUser = userService.updateProfile(userId, request);
            return Response.ok(ApiResponse.success(userMapper.toResponse(updatedUser))).build();
        } catch (Exception e) {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity(ApiResponse.error("Failed to update user profile"))
                    .build();
        }
    }

    @POST
    @Path("/avatar")
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @RolesAllowed({"USER", "ADMIN"})
    public Response uploadAvatar(@RestForm("file") FileUpload file) {
        try {
            UUID userId = currentUserService.getCurrentUserId();

            if (file == null || file.uploadedFile() == null || file.size() == 0) {
                return Response.status(Response.Status.BAD_REQUEST)
                        .entity(ApiResponse.error("No avatar file uploaded"))
                        .build();
            }

            String contentType = file.contentType();
            if (contentType == null || contentType.isBlank()) {
                return Response.status(Response.Status.BAD_REQUEST)
                        .entity(ApiResponse.error("Avatar content type is missing"))
                        .build();
            }

            byte[] imageBytes = Files.readAllBytes(file.uploadedFile());
            String avatarPath = userService.upsertCustomAvatar(userId, imageBytes, contentType);
            return Response.ok(ApiResponse.success(Map.of("avatar", avatarPath))).build();
        } catch (IllegalArgumentException e) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(ApiResponse.error(e.getMessage()))
                    .build();
        } catch (Exception e) {
            log.error("Failed to upload avatar", e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity(ApiResponse.error("Failed to upload avatar"))
                    .build();
        }
    }

    @GET
    @Path("/{userId}/avatar")
    @Produces({"image/jpeg", "image/png", "image/webp"})
    @RolesAllowed({"USER", "ADMIN"})
    public Response getUserAvatar(@PathParam("userId") UUID userId, @HeaderParam("If-None-Match") String ifNoneMatch) {
        try {
            Optional<UserAvatarEntity> avatarOpt = userService.findUserAvatar(userId);
            if (avatarOpt.isEmpty()) {
                return Response.status(Response.Status.NOT_FOUND).build();
            }

            UserAvatarEntity avatar = avatarOpt.get();
            String etag = "\"" + avatar.getUpdatedAt().toEpochMilli() + "-" + avatar.getSizeBytes() + "\"";
            if (etag.equals(ifNoneMatch)) {
                return Response.notModified()
                        .header("ETag", etag)
                        .header("Cache-Control", "private, no-cache, max-age=0")
                        .build();
            }

            return Response.ok(avatar.getImageData(), avatar.getContentType())
                    .header("ETag", etag)
                    .header("Cache-Control", "private, no-cache, max-age=0")
                    .build();
        } catch (Exception e) {
            log.error("Failed to load avatar for user {}", userId, e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
        }
    }

    @POST
    @Path("/changePassword")
    @RolesAllowed({"USER", "ADMIN"})
    public Response changePassword(@Valid UpdateUserPasswordRequest request) {
        try {
            UUID userId = currentUserService.getCurrentUserId();
            userService.changePassword(userId, request);
            return Response.ok(ApiResponse.success(java.util.Map.of("hasPassword", true))).build();
        } catch (InvalidPasswordException e) {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity(ApiResponse.error("Invalid password"))
                    .build();
        } catch (Exception e) {
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity(ApiResponse.error("Failed to change the password"))
                    .build();
        }
    }

    @PUT
    @RolesAllowed({"USER", "ADMIN"})
    @Path("/preferences/timeline")
    public Response updateTimelinePreferences(@Valid UpdateTimelinePreferencesRequest request) {
        UUID userId = currentUserService.getCurrentUserId();
        log.info("Updating timeline preferences for user {}", userId);
        log.debug("Timeline preferences: {}", request);

        // Update preferences within transaction
        String changeType = userService.updateTimelinePreferences(userId, request);

        // Create async job AFTER transaction commits (if needed)
        if ("structural".equals(changeType)) {
            UUID jobId = userService.createTimelineRegenerationJob(userId);
            if (jobId != null) {
                return Response.ok(ApiResponse.success(java.util.Map.of("jobId", jobId.toString()))).build();
            }
        }
        if ("boat-setup".equals(changeType)) {
            var setup = boatSetupService.startSetup(userId);
            Map<String, Object> payload = new LinkedHashMap<>();
            if (setup.jobId() != null) {
                payload.put("boatSetupJobId", setup.jobId().toString());
            }
            payload.put("boatSetupStatus", setup.status());
            return Response.ok(ApiResponse.success(payload)).build();
        }

        // No job created (classification-only or no changes)
        return Response.noContent().build();
    }

    @DELETE
    @RolesAllowed({"USER", "ADMIN"})
    @Path("/preferences/timeline")
    public Response resetPreferencesToDefaults() {
        UUID userId = currentUserService.getCurrentUserId();

        // Reset preferences within transaction
        boolean needsRegeneration = userService.resetTimelinePreferencesToDefaults(userId);

        // Create async job AFTER transaction commits
        if (needsRegeneration) {
            UUID jobId = userService.createTimelineRegenerationJob(userId);
            if (jobId != null) {
                return Response.ok(ApiResponse.success(java.util.Map.of("jobId", jobId.toString()))).build();
            }
        }

        // No job created
        return Response.noContent().build();
    }

    /**
     * Update timeline display preferences.
     * These settings affect ONLY how timelines are rendered in the UI.
     * Changing these settings does NOT trigger timeline regeneration.
     *
     * @param request the display preferences update request
     * @return 204 No Content on success
     */
    @PUT
    @RolesAllowed({"USER", "ADMIN"})
    @Path("/preferences/timeline/display")
    public Response updateTimelineDisplayPreferences(@Valid UpdateTimelineDisplayPreferencesRequest request) {
        UUID userId = currentUserService.getCurrentUserId();
        log.info("Updating timeline display preferences for user {}", userId);
        log.debug("Timeline display preferences: {}", request);

        try {
            userService.updateTimelineDisplayPreferences(userId, request);
        } catch (IllegalArgumentException e) {
            return Response.status(Response.Status.BAD_REQUEST)
                    .entity(ApiResponse.error(e.getMessage()))
                    .build();
        }

        TimelineDisplayPreferences updatedPreferences = userService.getTimelineDisplayPreferences(userId);
        return Response.ok(ApiResponse.success(updatedPreferences)).build();
    }

    /**
     * Get timeline display preferences for the current user.
     *
     * @return the user's timeline display preferences
     */
    @GET
    @RolesAllowed({"USER", "ADMIN"})
    @Path("/preferences/timeline/display")
    public Response getTimelineDisplayPreferences() {
        UUID userId = currentUserService.getCurrentUserId();
        log.debug("Getting timeline display preferences for user {}", userId);

        TimelineDisplayPreferences preferences = userService.getTimelineDisplayPreferences(userId);
        return Response.ok(ApiResponse.success(preferences)).build();
    }

    /**
     * Get current user profile information.
     *
     * @return The current user's profile data
     */
    @GET
    @Path("/me")
    @RolesAllowed({"USER", "ADMIN"})
    public Response getCurrentUserProfile() {
        try {
            UserEntity user = currentUserService.getCurrentUser();
            UserResponse response = userMapper.toResponse(user);
            return Response.ok(ApiResponse.success(response)).build();
        } catch (Exception e) {
            log.error("Failed to fetch current user profile", e);
            return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
                    .entity(ApiResponse.error("Failed to fetch user profile"))
                    .build();
        }
    }
}
