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

import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.READ_RULES;
import static com.iqser.red.service.persistence.management.v1.processor.roles.ActionRoles.WRITE_RULES;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

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.security.access.prepost.PreAuthorize;
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.iqser.red.service.persistence.management.v1.processor.exception.BadRequestException;
import com.iqser.red.service.persistence.management.v1.processor.exception.FileUploadException;
import com.iqser.red.service.persistence.management.v1.processor.exception.NotFoundException;
import com.iqser.red.service.persistence.management.v1.processor.service.RulesValidationService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.AuditPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.FileStatusPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.service.persistence.RulesPersistenceService;
import com.iqser.red.service.persistence.management.v1.processor.utils.RulesValidationMapper;
import com.iqser.red.service.persistence.service.v1.api.external.resource.RulesResource;
import com.iqser.red.service.persistence.service.v1.api.shared.model.AuditCategory;
import com.iqser.red.service.persistence.service.v1.api.shared.model.RuleFileType;
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.rules.DroolsValidationResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesResponse;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequest;
import com.iqser.red.service.persistence.service.v1.api.shared.model.dossiertemplate.rules.RulesUploadRequestModel;
import com.knecon.fforesight.keycloakcommons.security.KeycloakSecurity;

import feign.FeignException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@RestController
@RequiredArgsConstructor
public class RulesController implements RulesResource {

    private static final String DOWNLOAD_FILE_NAME = "rules.drl";

    private final RulesPersistenceService rulesPersistenceService;
    private final RulesValidationService rulesValidationService;
    private final AuditPersistenceService auditPersistenceService;
    private final FileStatusPersistenceService fileStatusPersistenceService;


    @Override
    @PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
    public ResponseEntity<DroolsValidationResponse> upload(@RequestBody RulesUploadRequestModel rules) {

        RulesUploadRequest rulesUploadRequest = RulesUploadRequest.fromModel(rules);
        DroolsValidationResponse droolsValidationResponse = new DroolsValidationResponse();

        try {
            var droolsValidation = rulesValidationService.validateRules(rulesUploadRequest.getRuleFileType(), rulesUploadRequest.getRules());
            droolsValidationResponse = RulesValidationMapper.createFromDroolsValidation(droolsValidation);
            if (!droolsValidation.isCompiled()) {

                return new ResponseEntity<>(droolsValidationResponse, !rules.isDryRun() ? HttpStatus.UNPROCESSABLE_ENTITY : HttpStatus.OK);
            }
        } catch (FeignException e) {
            if (e.status() == HttpStatus.BAD_REQUEST.value()) {
                throw new BadRequestException("The provided rule string is not a valid drools rule file!");
            }
        }
        if (!rules.isDryRun()) {
            rulesPersistenceService.setRules(rulesUploadRequest.getRules(), rulesUploadRequest.getDossierTemplateId(), rulesUploadRequest.getRuleFileType());
            fileStatusPersistenceService.resetErrorCounter(rules.getDossierTemplateId());
        }

        auditPersistenceService.audit(AuditRequest.builder()
                                              .userId(KeycloakSecurity.getUserId())
                                              .objectId(rulesUploadRequest.getDossierTemplateId())
                                              .category(AuditCategory.DOSSIER_TEMPLATE.name())
                                              .message(String.format("%s Rules have been updated", rulesUploadRequest.getRuleFileType()))
                                              .build());

        return new ResponseEntity<>(droolsValidationResponse, HttpStatus.OK);
    }


    @Override
    @PreAuthorize("hasAuthority('" + READ_RULES + "')")
    public RulesResponse download(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {

        return download(dossierTemplateId, RuleFileType.ENTITY);
    }


    @Override
    @PreAuthorize("hasAuthority('" + READ_RULES + "')")
    public RulesResponse download(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId, @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType) {

        var ruleEntityOptional = rulesPersistenceService.getRules(dossierTemplateId, ruleFileType);
        if (ruleEntityOptional.isEmpty()) {
            throw new NotFoundException(String.format("No rule file of type %s found for dossierTemplateId %s", ruleFileType, dossierTemplateId));
        }
        return new RulesResponse(ruleEntityOptional.get().getValue(), dossierTemplateId, ruleEntityOptional.get().isTimeoutDetected());

    }


    @Override
    @PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
    public ResponseEntity<DroolsValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                                                               @RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
                                                               @RequestPart(name = "file") MultipartFile file) {

        return uploadFile(dossierTemplateId, RuleFileType.ENTITY, dryRun, file);
    }


    @Override
    @PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
    public ResponseEntity<DroolsValidationResponse> uploadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                                                               @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType,
                                                               @RequestParam(value = DRY_RUN_PARAMETER) boolean dryRun,
                                                               @RequestPart(name = "file") MultipartFile file) {

        try {
            return upload(new RulesUploadRequestModel(new String(file.getBytes(), StandardCharsets.UTF_8), dossierTemplateId, ruleFileType, dryRun));
        } catch (IOException e) {
            throw new FileUploadException("Could not upload file.", e);
        }
    }


    @Override
    @PreAuthorize("hasAuthority('" + READ_RULES + "')")
    public ResponseEntity<?> downloadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId) {

        return downloadFile(dossierTemplateId, RuleFileType.ENTITY);
    }


    @Override
    @PreAuthorize("hasAuthority('" + READ_RULES + "')")
    public ResponseEntity<?> downloadFile(@PathVariable(DOSSIER_TEMPLATE_PARAMETER_NAME) String dossierTemplateId,
                                          @PathVariable(RULE_FILE_TYPE_PARAMETER_NAME) RuleFileType ruleFileType) {

        byte[] data = download(dossierTemplateId, ruleFileType).getRules().getBytes(StandardCharsets.UTF_8);
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.TEXT_PLAIN);

        httpHeaders.add("Content-Disposition", "attachment; filename*=utf-8''" + ruleFileType.name() + "_" + DOWNLOAD_FILE_NAME);
        InputStream is = new ByteArrayInputStream(data);

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


    @Override
    @PreAuthorize("hasAuthority('" + WRITE_RULES + "')")
    public void unlockRules(String dossierTemplateId, RuleFileType ruleFileType) {

        rulesPersistenceService.resetTimeoutDetected(dossierTemplateId, ruleFileType);
    }

}
