 package com.iqser.red.persistence.service.v1.external.api.impl.controller;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;
import com.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.service.DictionaryService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.knecon.fforesight.databasetenantcommons.providers.utils.MagicConverter;
import com.iqser.red.service.persistence.management.v1.processor.utils.StringEncodingUtils;
import com.iqser.red.service.persistence.management.v1.processor.utils.TypeValueMapper;
import com.iqser.red.service.persistence.service.v1.api.external.resource.DictionaryResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.CreateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.Dictionary;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.TypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.UpdateTypeValue;
import com.iqser.red.service.persistence.service.v1.api.shared.model.audit.AuditRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.configuration.Colors;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.DictionaryEntryType;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.type.Type;

import feign.FeignException;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequiredArgsConstructor
public class DictionaryController implements DictionaryResource {

    private final DictionaryService dictionaryService;
    private final AuditPersistenceService auditClient;


    @Override
    public void addEntry(@PathVariable(TYPE_PARAMETER_NAME) String type,
                         @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                         @RequestBody List<String> entries,
                         @RequestParam(name = REMOVE_CURRENT_REQUEST_PARAM, defaultValue = "false", required = false) boolean removeCurrent,
                         @RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
                         @RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType) {

        addEntries(type, dossierTemplateId, entries, removeCurrent, dossierId, dictionaryEntryType);
        auditClient.insertRecord(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierTemplateId)
                .category(AuditCategory.DICTIONARY.name())
                .message("Dictionary entries were added.")
                .details(Map.of("Type", type, "Number", entries.size()))
                .build());
    }


    private void addEntries(String type, String dossierTemplateId, List<String> entries, boolean removeCurrent, String dossierId, DictionaryEntryType dictionaryEntryType) {

        if (dossierId == null) {
            dictionaryService.addGlobalEntries(type, dossierTemplateId, entries, removeCurrent, dictionaryEntryType);
        } else {
            dictionaryService.addDossierEntries(type, dossierTemplateId, entries, removeCurrent, dossierId, dictionaryEntryType);
        }
    }


    @Override
    public void deleteEntry(@PathVariable(TYPE_PARAMETER_NAME) String type,
                            @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                            @PathVariable(ENTRY_PARAMETER_NAME) String entry,
                            @RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
                            @RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType) {

        deleteEntries(type, dossierTemplateId, Arrays.asList(entry), dossierId, dictionaryEntryType);
        auditClient.insertRecord(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierTemplateId)
                .category(AuditCategory.DICTIONARY.name())
                .message("Dictionary entry was deleted.")
                .details(Map.of("Type", type, "Value", entry))
                .build());
    }


    @Override
    public void deleteEntries(@PathVariable(TYPE_PARAMETER_NAME) String type,
                              @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                              @RequestBody List<String> entries,
                              @RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
                              @RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType) {

        if (dossierId == null) {
            dictionaryService.deleteGlobalEntries(type, dossierTemplateId, entries, dictionaryEntryType);
        } else {
            dictionaryService.deleteDossierEntries(type, dossierTemplateId, entries, dossierId, dictionaryEntryType);
        }

        auditClient.insertRecord(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierTemplateId)
                .category(AuditCategory.DICTIONARY.name())
                .message("Dictionary entries were deleted.")
                .details(Map.of("Type", type, "Number", entries.size()))
                .build());
    }


    @Override
    public void updateType(@PathVariable(TYPE_PARAMETER_NAME) String type,
                           @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                           @RequestBody UpdateTypeValue typeValue) {

        dictionaryService.updateGlobalType(type, dossierTemplateId, typeValue);

        auditClient.insertRecord(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierTemplateId)
                .category(AuditCategory.DICTIONARY.name())
                .message("Dictionary type was updated.")
                .details(Map.of("Type", type))
                .build());
    }


    @Override
    public TypeValue addType(@Valid @RequestBody CreateTypeValue typeValue) {

        Type result = dictionaryService.addGlobalType(typeValue);

        auditClient.insertRecord(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(typeValue.getDossierTemplateId())
                .category(AuditCategory.DICTIONARY.name())
                .message("Dictionary type was added.")
                .details(Map.of("Type", typeValue.getType()))
                .build());

        return MagicConverter.convert(result, TypeValue.class, new TypeValueMapper());
    }


    @Override
    public void deleteType(@PathVariable(TYPE_PARAMETER_NAME) String type,
                           @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {

        dictionaryService.deleteGlobalType(type, dossierTemplateId);

        auditClient.insertRecord(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierTemplateId)
                .category(AuditCategory.DICTIONARY.name())
                .message("Dictionary type was deleted.")
                .details(Map.of("Type", type))
                .build());
    }


    @Override
    public void deleteTypes(@RequestBody List<String> types,
                            @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {

        List<String> errorIds = new ArrayList<>();

        for (var type : types) {
            try {
                dictionaryService.deleteGlobalType(type, dossierTemplateId);

                auditClient.insertRecord(AuditRequest.builder()
                        .userId(KeycloakSecurity.getUserId())
                        .objectId(dossierTemplateId)
                        .category(AuditCategory.DICTIONARY.name())
                        .message("Dictionary type was deleted.")
                        .details(Map.of("Type", type))
                        .build());
            } catch (FeignException e) {
                errorIds.add(type);
            }
        }

        if (errorIds.size() > 0) {
            throw new BadRequestException("Failed to delete dictionary types: " + errorIds);
        }
    }


    @Override
    public TypeResponse getAllTypes(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                                    @RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
                                    @RequestParam(value = INCLUDE_DELETED_PARAMETER_NAME, required = false, defaultValue = "false") boolean includeDeleted) {

        return dictionaryService.getAllTypes(dossierTemplateId, dossierId, includeDeleted);
    }


    @Override
    public Colors getColors(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {

        return dictionaryService.getColors(dossierTemplateId);
    }


    @Override
    public void uploadDictionary(@RequestPart(name = "file", required = false) MultipartFile file,
                                 @PathVariable(TYPE_PARAMETER_NAME) String type,
                                 @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                                 @RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
                                 @RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType) {

        validateFile(file);

        try {
            addEntries(type, dossierTemplateId, new String(file.getBytes(), StandardCharsets.UTF_8).lines().collect(Collectors.toList()), true, dossierId, dictionaryEntryType);
        } catch (IOException e) {
            log.debug(e.getMessage(), e);
            throw new BadRequestException("Could not upload file.", e);
        }

        auditClient.insertRecord(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierTemplateId)
                .category(AuditCategory.DICTIONARY.name())
                .message("Dictionary has been uploaded.")
                .details(Map.of("Type", type))
                .build());
    }


    private void validateFile(@RequestPart(name = "file", required = false) MultipartFile file) {

        if (file == null || file.isEmpty()) {
            throw new BadRequestException("File cannot be null or empty");
        }
    }


    @Override
    public ResponseEntity<?> downloadDictionary(@PathVariable(TYPE_PARAMETER_NAME) String type,
                                                @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                                                @RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId,
                                                @RequestParam(value = DICTIONARY_ENTRY_TYPE_PARAM, required = false, defaultValue = DEFAULT_DICTIONARY_ENTRY_TYPE) DictionaryEntryType dictionaryEntryType) {

        byte[] data = null;
        switch (dictionaryEntryType) {
            case ENTRY:
                data = String.join("\n", getDictionaryForType(type, dossierTemplateId, dossierId).getEntries()).getBytes();
                break;
            case FALSE_POSITIVE:
                data = String.join("\n", getDictionaryForType(type, dossierTemplateId, dossierId).getFalsePositiveEntries()).getBytes();
                break;
            case FALSE_RECOMMENDATION:
                data = String.join("\n", getDictionaryForType(type, dossierTemplateId, dossierId).getFalseRecommendationEntries()).getBytes();
                break;
        }

        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.TEXT_PLAIN);

        httpHeaders.add("Content-Disposition", "attachment; filename*=utf-8''" + StringEncodingUtils.urlEncode(type) + ".txt");
        InputStream is = new ByteArrayInputStream(data);

        return new ResponseEntity<>(new InputStreamResource(is), httpHeaders, HttpStatus.OK);
    }


    @Override
    public Dictionary getDictionaryForType(@PathVariable(TYPE_PARAMETER_NAME) String type,
                                           @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                                           @RequestParam(value = DOSSIER_ID_PARAMETER_NAME, required = false) String dossierId) {

        return dictionaryService.getDictionaryForType(type, dossierTemplateId, dossierId);
    }

    @Override
    public Dictionary getMergedDictionaries(@PathVariable(TYPE_PARAMETER_NAME) String type,
                                                  @PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                                                  @RequestParam(value = DOSSIER_ID_PARAMETER_NAME) String dossierId) {
        return dictionaryService.getMergedDictionaryForType(type, dossierTemplateId, dossierId);
    }



    @Override
    public void setColors(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @RequestBody Colors colors) {

        dictionaryService.setColors(dossierTemplateId, colors);

        auditClient.insertRecord(AuditRequest.builder()
                .userId(KeycloakSecurity.getUserId())
                .objectId(dossierTemplateId)
                .category(AuditCategory.DOSSIER_TEMPLATE.name())
                .message("Colors have been changed.")
                .build());
    }

}
