package com.r11.tools.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.r11.tools.model.MockedMessageDto;
import com.r11.tools.service.KlausService;
import com.r11.tools.utilis.BusinessKey;
import com.r11.tools.utilis.TrackingClient;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDateTime;
import java.util.*;

/**
 * Returns the homepage and provides the api for javascript async requests.
 * @author Gabriel Modzelewski
 */
@RestController
@RequestMapping(path = "/api/mock")
@AllArgsConstructor
public class MockController {
    private final KlausService klausService;
    private final Logger log = LogManager.getRootLogger();

    @ExceptionHandler(Exception.class)
    public void errorHandler(Exception ex){
        log.error(ex.getStackTrace());
    }

    /**
     * Updates queried message with given set of data
     * @param body {@link MockedMessageDto} json representation
     * @return confirmation and 200 OK
     */
    @SneakyThrows
    @PutMapping
    public ResponseEntity<String> updateMessage(@RequestBody String body){
        ObjectMapper mapper = new ObjectMapper();
        MockedMessageDto message = mapper.readValue(body, MockedMessageDto.class);
        return klausService.setMockedResponse(message);
    }

    /**
     * Returns the full list of messages. It's used by javascript on the client side to initialize homepage
     * with data from the database.
     * @param uuidValue the key-uuid of given set of messages
     * @return responds with 200 OK and list of {@link MockedMessageDto}
     */
    @GetMapping({"/", "/{uuidValue}"})
    public List<MockedMessageDto> getListOfMessages(@PathVariable(required = false) String uuidValue){
        UUID clientUUID;
        if(uuidValue == null || uuidValue.equals("")) clientUUID = UUID.randomUUID();
        else clientUUID = UUID.fromString(uuidValue);
        List<MockedMessageDto> messages = klausService.getAllMockedResponses(clientUUID);
        if(messages.size() == 0) {
            klausService.setMockedResponse(buildDefaultMessage(clientUUID));
            messages = klausService.getAllMockedResponses(clientUUID);
        }
        Collections.sort(messages);
        return messages;
    }

    /**
    * If provided UUID does not exist in database returns ResponseEntity with new generated UUID(if previous UUID is not provided),
    * or old UUID(if previous UUID is provided). If provided UUID exists function returns provided UUID.
    * @param givenUUIDValue the UUID client wants to check exsitance in database
    * @param previousUUIDValue the previous UUID used by client(optional variable)
    * @return ResponseEntity with UUID
     */
    @RequestMapping(
            method = RequestMethod.GET ,
            path = {"/check/{givenUUIDValue}/{previousUUIDValue}",
                    "/check/{givenUUIDValue}"})
    public ResponseEntity<String> checkUUID(
            @PathVariable String givenUUIDValue
            ,@PathVariable(required = false) String previousUUIDValue  ){
        try{
            UUID.fromString(givenUUIDValue);
        } catch (IllegalArgumentException ex){
            log.error("Wrong UUID value!");
            if (previousUUIDValue == null || previousUUIDValue.equals("")){
                UUID newUUID = UUID.randomUUID();
                log.info("New UUID generated.");
                return ResponseEntity.ok(newUUID.toString());
            }
            log.info("Previous UUID value restored.");
            return ResponseEntity.ok(previousUUIDValue);
        }
        return ResponseEntity.ok(givenUUIDValue);
    }

    /**
     * Accepts empty post request and creates new message in given set. The new message has default set of data,
     * which is constructed in {@link #buildDefaultMessage(UUID, int)} method.
     * @param uuidValue the key-uuid of given set of messages
     * @return confirmation response with 200 OK
     */
    @PostMapping("/{uuidValue}")
    public ResponseEntity<String> addNewMessage(@PathVariable String uuidValue){
        UUID clientUUID = UUID.fromString(uuidValue);
        List<MockedMessageDto> messages = klausService.getAllMockedResponses(clientUUID);
        MockedMessageDto nextMessage = buildDefaultMessage(clientUUID, findNextId(messages));
        return klausService.setMockedResponse(nextMessage);
    }

    /**
     * Deletes message of given id via client request
     * @param uuidValue the key-uuid of given set of messages
     * @param idValue unique id of given message
     * @return after deletion the confirmation is send with status 200 OK
     */
    @DeleteMapping("/{uuidValue}/{idValue}")
    public ResponseEntity<String> removeMessage(@PathVariable String uuidValue,
                                                @PathVariable String idValue){
        UUID clientUUID = UUID.fromString(uuidValue);
        int id = Integer.parseInt(idValue);
        return klausService.deleteMockedResponse(clientUUID, id);
    }


    /**
     * Recalls {@link #buildDefaultMessage(UUID)} for message construction and sets id of message
     * @param uuid the key-uuid of given set of messages
     * @param id unique id of given message
     * @return message with default dataset and set id
     */
    private static MockedMessageDto buildDefaultMessage(UUID uuid, int id){
        MockedMessageDto message = buildDefaultMessage(uuid);
        message.setMockedResponseId(id);
        return message;
    }

    /**
     * Constructs message with default set of data
     * @param uuid the key-uuid of given set of messages
     * @return message with default dataset
     */
    private static MockedMessageDto buildDefaultMessage(UUID uuid){
        Map<String, String> headers = new HashMap<>();
        headers.put("Keep-Alive", "timeout=60");
        headers.put("Connection", "keep-alive");
        headers.put("Date", LocalDateTime.now().toString());
        return MockedMessageDto.builder()
                .clientUUID(uuid)
                .mockedResponseId(1)
                .mediaType(MediaType.APPLICATION_XML_VALUE)
                .messageBody("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                        "<note>\n" +
                        "  <to>Tove</to>\n" +
                        "  <from>Jani</from>\n" +
                        "  <heading>Reminder</heading>\n" +
                        "  <body>Don't forget me this weekend!</body>\n" +
                        "</note>")
                .httpHeaders(headers)
                .httpStatus(200)
                .build();
    }

    /**
     * Finds the highest id in the list and returns it incremented by 1
     * @param messages list of messages
     * @return highest id incremented by 1
     */
    public static int findNextId(List<MockedMessageDto> messages) {
        int highestId = 0;
        for (MockedMessageDto m : messages)
            highestId = highestId > m.getMockedResponseId() ? highestId : m.getMockedResponseId();
        return ++highestId;
    }

    /**
     * It's one of the most important features - the bread and butter of the Mocked Service. It's link that allows
     * to receive mocked response from the server and use it to mock!
     * @param requestEntity Logs the data of request
     * @param clientUUID the key-uuid of given set of messages
     * @param mockedResponseId unique id of given message
     * @return
     */
    @GetMapping(value = "/r/{clientUUID}/{mockedResponseId}")
    public ResponseEntity getMockedResponse(RequestEntity<String> requestEntity,
                                            @PathVariable UUID clientUUID,
                                            @PathVariable int mockedResponseId) {
        TrackingClient.setBusinessKeys(Map.of(BusinessKey.INTERFACE_NAME, "getMockedResponse - request",
                BusinessKey.CLIENT_UUID, String.valueOf(clientUUID),
                BusinessKey.MESSAGE_ID, String.valueOf(mockedResponseId)));
        // log.info(requestEntity.toString().replaceAll("\"", "\\\\\"").substring(1).replaceAll("\n",""));
        TrackingClient.setBusinessKeys(Map.of(BusinessKey.INTERFACE_NAME, "getMockedResponse - response",
                BusinessKey.CLIENT_UUID, String.valueOf(clientUUID),
                BusinessKey.MESSAGE_ID, String.valueOf(mockedResponseId)));
        MockedMessageDto mockedMessageDto = klausService.getMockedResponse(clientUUID, mockedResponseId);
        HttpHeaders httpHeaders = new HttpHeaders();
        if (mockedMessageDto.getHttpHeaders() != null) mockedMessageDto.getHttpHeaders().forEach(httpHeaders::set);
        return new ResponseEntity<>(mockedMessageDto.getMessageBody(), httpHeaders,
                Objects.requireNonNull(HttpStatus.valueOf(mockedMessageDto.getHttpStatus())));
    }


}
