package com.ease.gsms.server.controllers.api.v1;

import com.ease.gsms.server.model.Device;
import com.ease.gsms.server.model.DeviceInfo;
import com.ease.gsms.server.model.Sim;
import com.ease.gsms.server.services.DeviceService;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.messaging.simp.annotation.SendToUser;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.socket.messaging.SessionDisconnectEvent;

import javax.annotation.PostConstruct;
import java.security.Principal;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


@RestController
@RequestMapping(value = "/api/v1/device", produces = "application/json")
@Validated
public class DeviceController {

    @Autowired
    DeviceService deviceService;

    @Autowired
    Logger logger;

    @Autowired
    private SimpMessagingTemplate simpMessagingTemplate;

    private Map<String, String> onlineSenderCache;

    @PostConstruct
    private void init() {

        onlineSenderCache = new ConcurrentHashMap<>();

        deviceService.addObserver((device, sims) -> {
            simpMessagingTemplate.convertAndSendToUser(device.getOwner(), "/topic/device/device-info", new DeviceInfo(device, sims));
        });

    }

    @EventListener
    public void onDisconnect(SessionDisconnectEvent event) {
        String deviceUuid = onlineSenderCache.get(event.getSessionId());
        if (deviceUuid != null) {
            deviceService.offline(deviceUuid);
        }
    }

    @RequestMapping(value = "/user-devices", method = RequestMethod.GET)
    @PreAuthorize("hasRole('SENDER') || hasRole('SUBMITTER')")
    @Operation(
            summary = "Returns all devices accessible for sending by the current user",
            description = "Requires being an authenticated user with the SUBMITTER or SENDER role",
            security = @SecurityRequirement(name = "api-key")
    )
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "Devices could be successfully listed")
    })
    public ResponseEntity<DeviceInfo[]> getUserDevices(
            Principal principal
    ) {

        return ResponseEntity.ok(
                deviceService.getUserDevices(principal.getName())
        );

    }

    @RequestMapping(value = "/{uuid}", method = RequestMethod.GET)
    @PreAuthorize("hasRole('SENDER') || hasRole('SUBMITTER')")
    @Operation(
            summary = "Returns the device with the given UUID",
            description = "Requires being an authenticated user with the SUBMITTER or SENDER role",
            security = @SecurityRequirement(name = "api-key")
    )
    @ApiResponses(value = {
            @ApiResponse(responseCode = "200", description = "Device was found and data can be provided"),
            @ApiResponse(responseCode = "403", description = "Requesting user has no access to device"),
            @ApiResponse(responseCode = "404", description = "Device not found")
    })
    public ResponseEntity<DeviceInfo> getDeviceByUuid(
            @PathVariable("uuid") String uuid,
            Principal principal
    ) {

        DeviceInfo deviceInfo = deviceService.getDeviceInfoByUuid(uuid);

        if (deviceInfo == null) {

            return ResponseEntity
                    .notFound()
                    .build();

        } else {

            if (principal.getName().equals(deviceInfo.getDevice().getOwner())) {

                return ResponseEntity.ok(deviceInfo);

            } else {

                return ResponseEntity.status(403).build();

            }

        }

    }

    @Hidden
    @RequestMapping(value = "/update", method = RequestMethod.POST)
    @PreAuthorize("hasRole('SENDER')")
    public ResponseEntity<Long> update(
            @RequestBody DeviceInfo deviceInfo,
            Principal principal
    ) {

        Device device = deviceInfo.getDevice();

        Sim[] sims = deviceInfo.getSims();

        device.setOwner(principal.getName());

        device.setLastSeen(new Date());

        for (Sim sim : sims) {

            sim.setLastSeen(new Date());

        }

        deviceService.update(device, sims);

        return ResponseEntity.ok(device.getId());

    }

    @MessageMapping("/device/sender-online")
    @SendToUser("/topic/sender-registered")
    public Long senderOnline(DeviceInfo deviceInfo, Principal principal, @Header("simpSessionId") String sessionId) {

        Long id = update(deviceInfo, principal).getBody();

        onlineSenderCache.put(sessionId, deviceInfo.getDevice().getUuid());

        return id;

    }

    @MessageMapping("/device/sender-offline")
    public void senderOffline(String deviceUuid, Principal principal) {

        deviceService.offline(deviceUuid);

    }

}
