package ru.soune.nocopy.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
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.tokenoperation.TokenOperationPageDto;
import ru.soune.nocopy.dto.tokenoperation.TokenOperationRequest;
import ru.soune.nocopy.entity.tokenoperation.OperationType;
import ru.soune.nocopy.entity.tokenoperation.TokenOperation;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.tokenoperation.TokenOperationService;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
@RequiredArgsConstructor
public class TokenOperationHandler implements RequestHandler {
    private final TokenOperationService tokenOperationService;

    private final ObjectMapper objectMapper;

    private final AuthService authService;

    @Override
    public BaseResponse handle(BaseRequest request) throws Exception {
        log.info("Handling TokenOperation request: msg_id={}, version={}",
                request.getMsgId(), request.getVersion());

        try {
            TokenOperationRequest tokenRequest = objectMapper.convertValue(
                    request.getMessageBody(),
                    TokenOperationRequest.class
            );

            String action = tokenRequest.getAction();
            if (action == null) {
                return new BaseResponse(
                        request.getMsgId(),
                        MessageCode.INVALID_ACTION.getCode(),
                        MessageCode.INVALID_ACTION.getDescription(),
                        Map.of("error", "Action is required")
                );
            }

            switch (action.toLowerCase()) {
                case "get_list":
                    return handleGetList(request.getMsgId(), tokenRequest);
                case "get_by_id":
                    return handleGetById(request.getMsgId(), tokenRequest);
                case "get_by_user":
                    return handleGetByUser(request.getMsgId(), tokenRequest);
                case "create":
                    return handleCreate(request.getMsgId(), tokenRequest);
                case "update":
                    return handleUpdate(request.getMsgId(), tokenRequest);
                case "delete":
                    return handleDelete(request.getMsgId(), tokenRequest);
                case "delete_all_by_user":
                    return handleDeleteAllByUser(request.getMsgId(), tokenRequest);
                case "get_stats":
                    return handleGetStats(request.getMsgId(), tokenRequest);
                case "filter":
                    return handleFilter(request.getMsgId(), tokenRequest);
                default:
                    return new BaseResponse(
                            request.getMsgId(),
                            MessageCode.INVALID_ACTION.getCode(),
                            MessageCode.INVALID_ACTION.getDescription(),
                            Map.of("available_actions",
                                    "get_list, get_by_id, get_by_user, create, update, " +
                                            "delete, delete_all_by_user, get_stats, filter")
                    );
            }

        } catch (Exception e) {
            log.error("Error handling TokenOperation request", e);
            return new BaseResponse(
                    request.getMsgId(),
                    MessageCode.INTERNAL_ERROR.getCode(),
                    MessageCode.INTERNAL_ERROR.getDescription(),
                    Map.of("error", e.getMessage())
            );
        }
    }

    private BaseResponse handleGetList(Integer msgId, TokenOperationRequest request) {
        if (request.getToken() == null) {
            return new BaseResponse(
                    msgId,
                    MessageCode.USER_NOT_FOUND.getCode(),
                    MessageCode.USER_NOT_FOUND.getDescription(),
                    Map.of("error", "User ID is required")
            );
        }

        Long userId = authService.useUserAuthToken(request.getToken());

        try {
            Page<TokenOperation> page = tokenOperationService.findByUserId(
                    userId,
                    request.getPage() != null ? request.getPage() : 0,
                    request.getSize() != null ? request.getSize() : 20
            );

            TokenOperationPageDto pageDto = TokenOperationPageDto.builder()
                    .content(page.getContent())
                    .pageNumber(page.getNumber())
                    .pageSize(page.getSize())
                    .totalElements(page.getTotalElements())
                    .totalPages(page.getTotalPages())
                    .last(page.isLast())
                    .first(page.isFirst())
                    .build();

            Map<String, Object> responseData = new HashMap<>();
            responseData.put("operations", pageDto);
            responseData.put("total_elements", page.getTotalElements());
            responseData.put("total_pages", page.getTotalPages());
            responseData.put("current_page", page.getNumber());
            responseData.put("page_size", page.getSize());

            return new BaseResponse(
                    msgId,
                    MessageCode.SUCCESS.getCode(),
                    MessageCode.SUCCESS.getDescription(),
                    responseData
            );

        } catch (Exception e) {
            log.error("Error getting token operations list", e);
            return new BaseResponse(
                    msgId,
                    MessageCode.INTERNAL_ERROR.getCode(),
                    MessageCode.INTERNAL_ERROR.getDescription(),
                    Map.of("error", "Error getting operations: " + e.getMessage())
            );
        }
    }

    private BaseResponse handleGetById(Integer msgId, TokenOperationRequest request) {
        log.info("Getting token operation by id: {}", request.getOperationId());

        if (request.getOperationId() == null) {
            return new BaseResponse(
                    msgId,
                    MessageCode.OPERATION_NOT_FOUND.getCode(),
                    MessageCode.OPERATION_NOT_FOUND.getDescription(),
                    Map.of("error", "Operation ID is required")
            );
        }

        try {
            TokenOperation operation = tokenOperationService.findById(request.getOperationId());

            return new BaseResponse(
                    msgId,
                    MessageCode.SUCCESS.getCode(),
                    MessageCode.SUCCESS.getDescription(),
                    operation
            );

        } catch (jakarta.persistence.EntityNotFoundException e) {
            return new BaseResponse(
                    msgId,
                    MessageCode.NOT_FOUND.getCode(),
                    MessageCode.NOT_FOUND.getDescription(),
                    Map.of("error", "Operation not found with id: " + request.getOperationId())
            );
        } catch (Exception e) {
            log.error("Error getting token operation by id", e);
            return new BaseResponse(
                    msgId,
                    MessageCode.INTERNAL_ERROR.getCode(),
                    MessageCode.INTERNAL_ERROR.getDescription(),
                    Map.of("error", e.getMessage())
            );
        }
    }

    private BaseResponse handleGetByUser(Integer msgId, TokenOperationRequest request) {
        return handleGetList(msgId, request);
    }

    private BaseResponse handleCreate(Integer msgId, TokenOperationRequest request) {
        if (request.getToken() == null) {
            return new BaseResponse(
                    msgId,
                    MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
                    MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
                    Map.of("error", "User ID is required")
            );
        }

        Long userId = authService.useUserAuthToken(request.getToken());

        try {
            TokenOperation operation = tokenOperationService.create(
                    request.getOperationType(),
                    userId,
                    request.getSpent()
            );

            return new BaseResponse(
                    msgId,
                    MessageCode.SUCCESS.getCode(),
                    MessageCode.SUCCESS.getDescription(),
                    operation
            );

        } catch (Exception e) {
            log.error("Error creating token operation", e);
            return new BaseResponse(
                    msgId,
                    MessageCode.INTERNAL_ERROR.getCode(),
                    MessageCode.INTERNAL_ERROR.getDescription(),
                    Map.of("error", "Error creating operation: " + e.getMessage())
            );
        }
    }

    private BaseResponse handleUpdate(Integer msgId, TokenOperationRequest request) {
        log.info("Updating token operation: {}", request.getOperationId());

        if (request.getOperationId() == null) {
            return new BaseResponse(
                    msgId,
                    MessageCode.OPERATION_NOT_FOUND.getCode(),
                    MessageCode.OPERATION_NOT_FOUND.getDescription(),
                    Map.of("error", "Operation ID is required")
            );
        }

        try {
            TokenOperation existing = tokenOperationService.findById(request.getOperationId());

            if (request.getOperationType() != null) {
                existing.setOperationType(request.getOperationType());
            }
            if (request.getSpent() != null) {
                existing.setSpent(request.getSpent());
            }

            TokenOperation updated = tokenOperationService.update(request.getOperationId(), existing);

            return new BaseResponse(
                    msgId,
                    MessageCode.SUCCESS.getCode(),
                    MessageCode.SUCCESS.getDescription(),
                    updated
            );

        } catch (jakarta.persistence.EntityNotFoundException e) {
            return new BaseResponse(
                    msgId,
                    MessageCode.NOT_FOUND.getCode(),
                    MessageCode.NOT_FOUND.getDescription(),
                    Map.of("error", "Operation not found with id: " + request.getOperationId())
            );
        } catch (Exception e) {
            log.error("Error updating token operation", e);
            return new BaseResponse(
                    msgId,
                    MessageCode.INTERNAL_ERROR.getCode(),
                    MessageCode.INTERNAL_ERROR.getDescription(),
                    Map.of("error", "Error updating operation: " + e.getMessage())
            );
        }
    }

    private BaseResponse handleDelete(Integer msgId, TokenOperationRequest request) {
        log.info("Deleting token operation: {}", request.getOperationId());

        if (request.getOperationId() == null) {
            return new BaseResponse(
                    msgId,
                    MessageCode.OPERATION_NOT_FOUND.getCode(),
                    MessageCode.OPERATION_NOT_FOUND.getDescription(),
                    Map.of("error", "Operation ID is required")
            );
        }

        try {
            tokenOperationService.deleteById(request.getOperationId());

            return new BaseResponse(
                    msgId,
                    MessageCode.SUCCESS.getCode(),
                    MessageCode.SUCCESS.getDescription(),
                    Map.of("message", "Operation deleted successfully",
                            "operation_id", request.getOperationId())
            );

        } catch (jakarta.persistence.EntityNotFoundException e) {
            return new BaseResponse(
                    msgId,
                    MessageCode.NOT_FOUND.getCode(),
                    MessageCode.NOT_FOUND.getDescription(),
                    Map.of("error", "Operation not found with id: " + request.getOperationId())
            );
        } catch (Exception e) {
            log.error("Error deleting token operation", e);
            return new BaseResponse(
                    msgId,
                    MessageCode.INTERNAL_ERROR.getCode(),
                    MessageCode.INTERNAL_ERROR.getDescription(),
                    Map.of("error", "Error deleting operation: " + e.getMessage())
            );
        }
    }

    private BaseResponse handleDeleteAllByUser(Integer msgId, TokenOperationRequest request) {
        if (request.getToken() == null) {
            return new BaseResponse(
                    msgId,
                    MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
                    MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
                    Map.of("error", "User ID is required")
            );
        }

        Long userId = authService.useUserAuthToken(request.getToken());

        try {
            tokenOperationService.deleteByUserId(userId);

            return new BaseResponse(
                    msgId,
                    MessageCode.SUCCESS.getCode(),
                    MessageCode.SUCCESS.getDescription(),
                    Map.of("message", "All operations deleted successfully for user",
                            "user_id", userId)
            );

        } catch (Exception e) {
            log.error("Error deleting all operations for user", e);
            return new BaseResponse(
                    msgId,
                    MessageCode.INTERNAL_ERROR.getCode(),
                    MessageCode.INTERNAL_ERROR.getDescription(),
                    Map.of("error", "Error deleting operations: " + e.getMessage())
            );
        }
    }

    private BaseResponse handleGetStats(Integer msgId, TokenOperationRequest request) {
        if (request.getToken() == null) {
            return new BaseResponse(
                    msgId,
                    MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
                    MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
                    Map.of("error", "User ID is required")
            );
        }

        Long userId = authService.useUserAuthToken(request.getToken());

        if (userId == null) {
            return new BaseResponse(
                    msgId,
                    MessageCode.USER_NOT_FOUND.getCode(),
                    MessageCode.USER_NOT_FOUND.getDescription(),
                    Map.of("error", "User ID is required")
            );
        }

        try {
            Map<String, Long> statsByType = new HashMap<>();
            long totalSpent = 0L;

            for (OperationType type : OperationType.values()) {
                Long spent = tokenOperationService.getTotalSpentByUserAndType(userId, type);
                statsByType.put(type.name().toLowerCase(), spent != null ? spent : 0L);
                totalSpent += spent != null ? spent : 0L;
            }

            Map<String, Object> statistics = new HashMap<>();
            statistics.put("user_id", userId);
            statistics.put("total_spent", totalSpent);
            statistics.put("total_operations", tokenOperationService.countByUserId(userId));
            statistics.put("stats_by_type", statsByType);

            return new BaseResponse(
                    msgId,
                    MessageCode.SUCCESS.getCode(),
                    MessageCode.SUCCESS.getDescription(),
                    statistics
            );

        } catch (Exception e) {
            log.error("Error getting statistics", e);
            return new BaseResponse(
                    msgId,
                    MessageCode.INTERNAL_ERROR.getCode(),
                    MessageCode.INTERNAL_ERROR.getDescription(),
                    Map.of("error", "Error getting statistics: " + e.getMessage())
            );
        }
    }

    private BaseResponse handleFilter(Integer msgId, TokenOperationRequest request) {
        if (request.getToken() == null) {
            return new BaseResponse(
                    msgId,
                    MessageCode.AUTH_TOKEN_NOT_FOUND.getCode(),
                    MessageCode.AUTH_TOKEN_NOT_FOUND.getDescription(),
                    Map.of("error", "User ID is required")
            );
        }

        Long userId = authService.useUserAuthToken(request.getToken());

        try {
            Page<TokenOperation> page = tokenOperationService.findByFilters(
                    userId,
                    request.getOperationType(),
                    request.getMinSpent(),
                    request.getMaxSpent(),
                    request.getPage() != null ? request.getPage() : 0,
                    request.getSize() != null ? request.getSize() : 20,
                    request.getSortBy() != null ? request.getSortBy() : "createdAt",
                    request.getSortDirection() != null ? request.getSortDirection() : "desc"
            );

            TokenOperationPageDto pageDto = TokenOperationPageDto.builder()
                    .content(page.getContent())
                    .pageNumber(page.getNumber())
                    .pageSize(page.getSize())
                    .totalElements(page.getTotalElements())
                    .totalPages(page.getTotalPages())
                    .last(page.isLast())
                    .first(page.isFirst())
                    .build();

            Map<String, Object> responseData = new HashMap<>();
            responseData.put("operations", pageDto);
            responseData.put("total_elements", page.getTotalElements());
            responseData.put("total_pages", page.getTotalPages());
            responseData.put("current_page", page.getNumber());
            responseData.put("page_size", page.getSize());

            responseData.put("applied_filters", Map.of(
                    "user_id",userId != null ? userId : "all",
                    "operation_type", request.getOperationType() != null ? request.getOperationType() : "all",
                    "min_spent", request.getMinSpent() != null ? request.getMinSpent() : "none",
                    "max_spent", request.getMaxSpent() != null ? request.getMaxSpent() : "none"
            ));

            return new BaseResponse(
                    msgId,
                    MessageCode.SUCCESS.getCode(),
                    MessageCode.SUCCESS.getDescription(),
                    responseData
            );

        } catch (Exception e) {
            log.error("Error filtering operations", e);
            return new BaseResponse(
                    msgId,
                    MessageCode.INTERNAL_ERROR.getCode(),
                    MessageCode.INTERNAL_ERROR.getDescription(),
                    Map.of("error", "Error filtering operations: " + e.getMessage())
            );
        }
    }
}