package ru.soune.nocopy.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.BaseRequest;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.complaint.LawCaseRequest;
import ru.soune.nocopy.dto.complaint.LawCaseResponse;
import ru.soune.nocopy.dto.complaint.PaginatedResponse;
import ru.soune.nocopy.entity.complaint.LawCase;
import ru.soune.nocopy.entity.complaint.LawCasePriority;
import ru.soune.nocopy.entity.complaint.LawCaseType;
import ru.soune.nocopy.entity.file.FileType;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.user.User;
import ru.soune.nocopy.entity.violation.Violation;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.exception.TariffNotFoundException;
import ru.soune.nocopy.exception.UserNotFoundException;
import ru.soune.nocopy.repository.UserRepository;
import ru.soune.nocopy.service.complaint.LawCaseService;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tariff.TariffConstants;
import ru.soune.nocopy.service.tariff.TariffInfoService;
import ru.soune.nocopy.service.violation.ViolationService;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Component
@RequiredArgsConstructor
public class LawCaseHandler implements RequestHandler {

    private final LawCaseService lawCaseService;

    private final AuthService authService;

    private final ObjectMapper objectMapper;

    private final UserRepository userRepository;

    private final TariffInfoService tariffInfoService;

    private final ViolationService violationService;

    @Override
    public BaseResponse handle(BaseRequest request) throws Exception {
        LawCaseRequest lawCaseRequest = objectMapper.convertValue(request.getMessageBody(),
                LawCaseRequest.class);
        Long userId = authService.useUserAuthToken(lawCaseRequest.getToken());
        String action = lawCaseRequest.getAction();

        return switch (action) {
            case "get_all" -> getAllUserLawCases(userId, lawCaseRequest, request);
            case "get_byId" -> getLawCaseById(lawCaseRequest.getId(), request);
            case "create" -> createLawCase(userId, lawCaseRequest, request);
            case "update_priority" -> updatePriority(lawCaseRequest, request);
            case "update_type" -> updateType(lawCaseRequest, request);
            case "update_amount" -> updateAmount(lawCaseRequest, request);
            case "delete" -> deleteLawCase(lawCaseRequest.getId(), request);
            default -> new BaseResponse(
                    request.getMsgId(),
                    MessageCode.INVALID_ACTION.getCode(),
                    MessageCode.INVALID_ACTION.getDescription(),
                    Map.of("actions", "get_all, get_byId, create, update_priority, update_type, " +
                            "update_amount, delete")
            );
        };
    }

    private BaseResponse getAllUserLawCases(Long userId, LawCaseRequest lawCaseRequest, BaseRequest request) {
        validateLawCaseRequestFilters(lawCaseRequest);
        LawCaseType lawCaseType = lawCaseRequest.getFilterType() != null ?
                LawCaseType.valueOf(lawCaseRequest.getFilterType().toUpperCase()) : null;
        LawCasePriority lawCasePriority = lawCaseRequest.getFilterPriority() != null ?
                LawCasePriority.valueOf(lawCaseRequest.getFilterPriority().toUpperCase()) : null;

        Page<LawCase> lawCases = lawCaseService.getAllUserLawCases(
                userId,
                lawCaseRequest.getPageSize(),
                lawCaseRequest.getPageNumber(),
                lawCaseType,
                lawCaseRequest.getFilterLawyer(),
                lawCasePriority,
                lawCaseRequest.getViolationId(),
                lawCaseRequest.getSortBy(),
                lawCaseRequest.getSortDir());

        List<LawCaseResponse> responses = new ArrayList<>();
        List<LawCase> content = lawCases.getContent();

        for (LawCase lawCase: content) {
            LawCaseResponse lawCaseResponse = LawCaseResponse.fromEntity(lawCase);

            Violation violation = violationService.getById(lawCase.getViolationId());
            if (violation != null) lawCaseResponse.setFileId(violation.getFileEntity().getId());

            responses.add(lawCaseResponse);
        }

        PaginatedResponse<LawCaseResponse> paginatedResponse = new PaginatedResponse<>(
                responses,
                lawCases.getTotalElements(),
                lawCases.getTotalPages(),
                lawCases.getNumber(),
                lawCases.getSize(),
                lawCases.isFirst(),
                lawCases.isLast());

        return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
                MessageCode.SUCCESS.getDescription(), paginatedResponse);
    }

    private void validateLawCaseRequestFilters(LawCaseRequest lawCaseRequest) {
        if (lawCaseRequest.getFilterPriority() != null) {
            validateEnumValue(lawCaseRequest.getFilterPriority(),
                    LawCasePriority.class,
                    "filterPriority");
        }

        if (lawCaseRequest.getFilterType() != null) {
            validateEnumValue(lawCaseRequest.getFilterType(),
                    LawCaseType.class,
                    "filterType");
        }
    }

    private <T extends Enum<T>> void validateEnumValue(String value, Class<T> enumClass, String fieldName) {
        boolean valid = Arrays.stream(enumClass.getEnumConstants())
                .anyMatch(e -> e.name().equals(value));

        if (!valid) {
            throw new NotValidFieldException("Not valid " + fieldName,
                    new BaseResponse(30017, MessageCode.NOT_VALID_FIELD.getCode(),
                            MessageCode.NOT_VALID_FIELD.getDescription(),
                            Map.of("field", fieldName,
                                    "valid_values", Arrays.stream(enumClass.getEnumConstants())
                                            .map(Enum::name)
                                            .collect(Collectors.toList()),
                                    "received_value", value)));
        }
    }

    private BaseResponse getLawCaseById(Long id, BaseRequest request) {
        LawCase lawCase = lawCaseService.getById(id);

        if (lawCase == null) {
            return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
                    MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", id));
        }

        return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
                MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
    }

    private BaseResponse createLawCase(Long userId, LawCaseRequest lawCaseRequest, BaseRequest request) {
        validateRequiredParameters(lawCaseRequest, request, userId);

        try {
            tariffInfoService.writeOffTokens(userId, TariffConstants.LEGAL_COST, OperationType.CREATE_LEGAL_CASE);
        } catch (TariffNotFoundException e) {
            return new BaseResponse(request.getMsgId(), MessageCode.USER_NOT_HAVE_TOKEN.getCode(),
                    MessageCode.USER_NOT_HAVE_TOKEN.getDescription(),
                    Map.of("error", "Tariff not found", "message", e.getMessage()));
        } catch (MessagingException | IOException e) {
            return new BaseResponse(request.getMsgId(), MessageCode.INVALID_FIELD.getCode(),
                    MessageCode.INVALID_FIELD.getDescription(), Map.of("id", lawCaseRequest.getId()));
        }

        User user = userRepository.findById(userId).orElseThrow(() -> new UserNotFoundException("User not found"));
        Long violationId = lawCaseRequest.getViolationId();

        LawCase lawCase = lawCaseService.addLawCase(
                lawCaseRequest.getAmount(),
                lawCaseRequest.getDescription(),
                lawCaseRequest.getName(),
                LawCasePriority.valueOf(lawCaseRequest.getPriority()),
                violationId, user);

        return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
                MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
    }

    private BaseResponse updatePriority(LawCaseRequest lawCaseRequest, BaseRequest request) {
        LawCase lawCase = lawCaseService.changePriority(
                lawCaseRequest.getId(),
                LawCasePriority.valueOf(lawCaseRequest.getPriority()));

        if (lawCase == null) {
            return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
                    MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", lawCaseRequest.getId()));
        }

        return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
                MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
    }

    private BaseResponse updateType(LawCaseRequest lawCaseRequest, BaseRequest request) {
        LawCase lawCase = lawCaseService.changeStatus(
                lawCaseRequest.getId(),
                LawCaseType.valueOf(lawCaseRequest.getType()));

        if (lawCase == null) {
            return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
                    MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", lawCaseRequest.getId()));
        }

        return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
                MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
    }

    private BaseResponse updateAmount(LawCaseRequest lawCaseRequest, BaseRequest request) {
        LawCase lawCase = lawCaseService.changeDamage(
                lawCaseRequest.getId(),
                lawCaseRequest.getAmount());

        if (lawCase == null) {
            return new BaseResponse(request.getMsgId(), MessageCode.LAW_CASE_NOT_FOUND.getCode(),
                    MessageCode.LAW_CASE_NOT_FOUND.getDescription(), Map.of("id", lawCaseRequest.getId()));
        }

        return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
                MessageCode.SUCCESS.getDescription(), LawCaseResponse.fromEntity(lawCase));
    }

    private BaseResponse deleteLawCase(Long id, BaseRequest request) {
        lawCaseService.deleteById(id);
        return new BaseResponse(request.getMsgId(), MessageCode.SUCCESS.getCode(),
                MessageCode.SUCCESS.getDescription(), Map.of("id", id));
    }

    private void validateRequiredParameters(LawCaseRequest lawCaseRequest, BaseRequest request, Long userId) {
        if (lawCaseRequest.getName() == null) throw new NotValidFieldException("Name is required",
                new BaseResponse(request.getMsgId(), MessageCode.NOT_VALID_FIELD.getCode(),
                        MessageCode.NOT_VALID_FIELD.getDescription(), Map.of("fields", "name")));

        if (lawCaseRequest.getDescription() == null) throw new NotValidFieldException("Description is required",
                new BaseResponse(request.getMsgId(), MessageCode.NOT_VALID_FIELD.getCode(),
                        MessageCode.NOT_VALID_FIELD.getDescription(), Map.of("fields", "description")));

        if (lawCaseRequest.getViolationId() == null) throw new NotValidFieldException("Violation id is required",
                new BaseResponse(request.getMsgId(), MessageCode.NOT_VALID_FIELD.getCode(),
                        MessageCode.NOT_VALID_FIELD.getDescription(), Map.of("fields", "violation_id")));

        Violation violation = violationService.getById(lawCaseRequest.getViolationId());

        if (!Objects.equals(violation.getFileEntity().getUserId(), userId))
            throw new NotValidFieldException("Not user violation",
                new BaseResponse(request.getMsgId(), MessageCode.NOT_VALID_FIELD.getCode(),
                        MessageCode.NOT_VALID_FIELD.getDescription(), Map.of("fields", "violation_id")));

        if (lawCaseRequest.getType() != null &&
                !FileType.DOCUMENT.getDisplayName().equals(lawCaseRequest.getType().toLowerCase()) &&
                !FileType.AUDIO.getDisplayName().equals(lawCaseRequest.getType().toLowerCase()) &&
                !FileType.IMAGE.getDisplayName().equals(lawCaseRequest.getType().toLowerCase()) &&
                !FileType.VIDEO.getDisplayName().equals(lawCaseRequest.getType().toLowerCase()))
            throw new NotValidFieldException("Not valid type",
                new BaseResponse(request.getMsgId(), MessageCode.NOT_VALID_FIELD.getCode(),
                        MessageCode.NOT_VALID_FIELD.getDescription(), Map.of("valid_types",
                        List.of(FileType.DOCUMENT.getDisplayName(),
                        FileType.AUDIO.getDisplayName(),
                        FileType.IMAGE.getDisplayName(),
                        FileType.VIDEO.getDisplayName()))));

        if (lawCaseRequest.getPriority() != null &&
                !LawCasePriority.HIGH.name().equalsIgnoreCase(lawCaseRequest.getPriority()) &&
                !LawCasePriority.LOW.name().equalsIgnoreCase(lawCaseRequest.getPriority()) &&
                !LawCasePriority.MIDDLE.name().equalsIgnoreCase(lawCaseRequest.getPriority()) &&
                !LawCasePriority.CRITICAL.name().equalsIgnoreCase(lawCaseRequest.getPriority()))
            throw new NotValidFieldException("Not valid type",
                    new BaseResponse(request.getMsgId(), MessageCode.NOT_VALID_FIELD.getCode(),
                            MessageCode.NOT_VALID_FIELD.getDescription(), Map.of("valid_types",
                            List.of(LawCasePriority.LOW.name(),
                                    LawCasePriority.MIDDLE.name(),
                                    LawCasePriority.HIGH.name(),
                                    LawCasePriority.CRITICAL.name()))));
    }
}
