package ru.soune.nocopy.handler;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import ru.soune.nocopy.dto.*;
import ru.soune.nocopy.dto.file.*;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.entity.file.FileStatus;
import ru.soune.nocopy.entity.file.ProtectionStatus;
import ru.soune.nocopy.exception.FileEntityNotFoundException;
import ru.soune.nocopy.exception.NotFoundAuthToken;
import ru.soune.nocopy.repository.FileEntityRepository;
import ru.soune.nocopy.service.register.AuthService;
import ru.soune.nocopy.service.file.FileEntityService;
import ru.soune.nocopy.service.file.FileStatsService;

import java.util.Arrays;
import java.util.List;
import java.util.Optional;

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

    private final FileEntityService fileEntityService;

    private final AuthService authService;

    private final FileStatsService fileStatsService;

    private final ObjectMapper objectMapper;

    private final FileEntityRepository fileEntityRepository;

    @Override
    public BaseResponse handle(BaseRequest request) {
        try {
            FileEntityRequest fileRequest = objectMapper.convertValue(
                    request.getMessageBody(), FileEntityRequest.class);

            String action = fileRequest.getAction();

            switch (action) {
                case "file_info":
                    return handleGetFileInfo(request, fileRequest);
                case "user_files_info":
                    return handleGetFilesUserInfo(request, fileRequest);
                case "file_by_session":
                    return handleGetFileBySession(request, fileRequest);
                case "user_files":
                    return handleGetUserFiles(request, fileRequest);
                case "search_files":
                    return handleSearchFiles(request, fileRequest);
                case "storage_usage":
                    return handleGetStorageUsage(request, fileRequest);
                case "check_protected":
                    return handleProtectFile(request, fileRequest);
                case "delete_file":
                    return handleDeleteFile(request, fileRequest);
                default:
                    ActionResponse response = ActionResponse.builder()
                            .action(action)
                            .availableActions(Arrays.asList(
                                    "file_info", "file_by_session", "user_files",
                                    "search_files", "storage_usage", "delete_file",
                                    "user_files_info"))
                            .build();

                    return new BaseResponse(request.getMsgId(),
                            MessageCode.INVALID_ACTION.getCode(),
                            "Invalid action: " + action,
                            response);
            }
        } catch (Exception e) {
            log.error("Error in FileEntityHandler", e);
            return new BaseResponse(request.getMsgId(),
                    MessageCode.FILE_UPLOAD_ERROR.getCode(),
                    "Handler error: " + e.getMessage(),
                    null);
        }
    }

    private BaseResponse handleGetFilesUserInfo(BaseRequest request, FileEntityRequest fileRequest) {
        Long userId = authService.useUserAuthToken(fileRequest.getToken());
        FileInfoUserResponse userFileStats = fileStatsService.getUserFileStats(userId);

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

    private BaseResponse handleGetFileInfo(BaseRequest request, FileEntityRequest fileRequest) {
        try {
            Long userId =  authService.useUserAuthToken(fileRequest.getToken());
            FileEntityResponse fileInfo = fileEntityService.getById(fileRequest.getFileId(), request.getVersion());

            if (!fileInfo.getUserId().equals(userId)) {
                return new BaseResponse(request.getMsgId(),
                        MessageCode.ACCESS_DENIED.getCode(),
                        "Access denied",
                        null);
            }

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

        } catch (NotFoundAuthToken e) {
            return new BaseResponse(request.getMsgId(),
                    MessageCode.INVALID_TOKEN.getCode(),
                    "Authentication required",
                    null);
        } catch (Exception e) {
            log.error("Error getting file info", e);
            return new BaseResponse(request.getMsgId(),
                    MessageCode.FILE_UPLOAD_ERROR.getCode(),
                    "Failed to get file info: " + e.getMessage(),
                    null);
        }
    }

    private BaseResponse handleGetFileBySession(BaseRequest request, FileEntityRequest fileRequest) {
        try {
            Long userId = authService.useUserAuthToken(fileRequest.getToken());
            FileEntityResponse fileInfo = fileEntityService.getByUploadSessionId(fileRequest.getUploadSessionId(),
                    request.getVersion());

            if (!fileInfo.getUserId().equals(userId)) {
                return new BaseResponse(request.getMsgId(),
                        MessageCode.ACCESS_DENIED.getCode(),
                        "Access denied",
                        null);
            }

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

        } catch (NotFoundAuthToken e) {
            return new BaseResponse(request.getMsgId(),
                    MessageCode.INVALID_TOKEN.getCode(),
                    "Authentication required",
                    null);
        } catch (Exception e) {
            log.error("Error getting file by session", e);
            return new BaseResponse(request.getMsgId(),
                    MessageCode.FILE_UPLOAD_ERROR.getCode(),
                    "Failed to get file: " + e.getMessage(),
                    null);
        }
    }

    private BaseResponse handleGetUserFiles(BaseRequest request, FileEntityRequest fileRequest) {
        try {
            Long userId = authService.useUserAuthToken(fileRequest.getToken());
            int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
            int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;

            FileResponse files = fileEntityService.getUserFiles(userId, page, pageSize, request.getVersion());

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

        } catch (NotFoundAuthToken e) {
            return new BaseResponse(request.getMsgId(),
                    MessageCode.INVALID_TOKEN.getCode(),
                    "Authentication required",
                    null);
        } catch (Exception e) {
            log.error("Error getting user files", e);
            return new BaseResponse(request.getMsgId(),
                    MessageCode.FILE_UPLOAD_ERROR.getCode(),
                    "Failed to get files: " + e.getMessage(),
                    null);
        }
    }

    private BaseResponse handleSearchFiles(BaseRequest request, FileEntityRequest fileRequest) {
        try {
            Long userId = authService.useUserAuthToken(fileRequest.getToken());
            int page = fileRequest.getPage() != null ? fileRequest.getPage() : 1;
            int pageSize = fileRequest.getPageSize() != null ? fileRequest.getPageSize() : 20;

            if (fileRequest.getQuery() == null || fileRequest.getQuery().isBlank()) {
                return handleGetUserFiles(request, fileRequest);
            }

            FileResponse allFiles = fileEntityService.getUserFiles(userId, 1, 1000, request.getVersion());

            var filteredFiles = allFiles.getFiles().stream()
                    .filter(f -> (f.getOriginalFileName() + f.getFileExtension()).toLowerCase()
                            .contains(fileRequest.getQuery().toLowerCase()))
                    .toList();

            int start = (page - 1) * pageSize;
            int end = Math.min(start + pageSize, filteredFiles.size());
            List<FileEntityResponse> fileEntityResponses = filteredFiles.subList(start, Math.min(end, filteredFiles.size()));
            long sumSize = fileEntityResponses.stream()
                    .mapToLong(FileEntityResponse::getFileSize)
                    .sum();

            FileListResponse response = FileListResponse.builder()
                    .files(fileEntityResponses)
                    .totalCount(filteredFiles.size())
                    .totalSize(sumSize)
                    .formattedTotalSize(fileEntityService.formatFileSize(sumSize))
                    .page(page)
                    .pageSize(pageSize)
                    .build();

            return new BaseResponse(request.getMsgId(),
                    MessageCode.SUCCESS.getCode(),
                    MessageCode.SUCCESS.getDescription(),
                    response);
        } catch (NotFoundAuthToken e) {
            return new BaseResponse(request.getMsgId(), MessageCode.INVALID_TOKEN.getCode(),
                    "Authentication required", null);
        } catch (Exception e) {
            log.error("Error searching files", e);
            return new BaseResponse(request.getMsgId(), MessageCode.FILE_UPLOAD_ERROR.getCode(),
                    "Failed to search files: " + e.getMessage(), null);
        }
    }

    private BaseResponse handleGetStorageUsage(BaseRequest request, FileEntityRequest fileRequest) {
        try {
            Long userId = authService.useUserAuthToken(fileRequest.getToken());
            long usage = fileEntityService.getUserStorageUsed(userId);

            StorageUsageResponse response = StorageUsageResponse.builder()
                    .userId(userId)
                    .storageUsed(usage)
                    .formattedStorageUsed(formatFileSize(usage))
                    .build();

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

        } catch (NotFoundAuthToken e) {
            return new BaseResponse(request.getMsgId(),
                    MessageCode.INVALID_TOKEN.getCode(),
                    "Authentication required",
                    null);
        } catch (Exception e) {
            log.error("Error getting storage usage", e);
            return new BaseResponse(request.getMsgId(),
                    MessageCode.FILE_UPLOAD_ERROR.getCode(),
                    "Failed to get storage usage: " + e.getMessage(),
                    null);
        }
    }

    private BaseResponse handleProtectFile(BaseRequest request, FileEntityRequest fileRequest) {
        String fileId = fileRequest.getFileId();
        Optional<FileEntity> optionalFileEntity = fileEntityRepository.findById(fileId);

        if (optionalFileEntity.isEmpty()) {
            return new BaseResponse(request.getMsgId(),
                    MessageCode.FILE_NOT_FOUND.getCode(),
                    "File not found exception: " + fileRequest.getFileId(),
                    null);
        }

        FileEntity fileEntity = optionalFileEntity.get();
        ProtectionStatus protectionStatus = fileEntity.getProtectionStatus();

        Integer code = protectionStatus == ProtectionStatus.PROTECTED ? MessageCode.FILE_IS_PROTECTED.getCode():
                MessageCode.FILE_IS_NOT_PROTECTED.getCode();
        String message = protectionStatus == ProtectionStatus.PROTECTED ? MessageCode.FILE_IS_PROTECTED.getDescription():
                MessageCode.FILE_IS_NOT_PROTECTED.getDescription();

        return new BaseResponse(request.getMsgId(), code, message, fileEntityService.getById(fileId,
                request.getVersion()));
    }

    private BaseResponse handleDeleteFile(BaseRequest request, FileEntityRequest fileRequest) {
        try {
            Long userId = authService.useUserAuthToken(fileRequest.getToken());
            String fileId = fileRequest.getFileId();
            FileEntityResponse fileInfo = fileEntityService.getById(fileId, request.getVersion());
            DeleteFileResponse response;

            if (!fileInfo.getUserId().equals(userId)) {
                return new BaseResponse(request.getMsgId(),
                        MessageCode.ACCESS_DENIED.getCode(),
                        "Access denied",
                        null);
            }

            FileEntity fileEntity = fileEntityRepository.findById(fileId)
                    .orElseThrow(() -> new FileEntityNotFoundException(fileId));
            int fullDelete = fileRequest.getFullDelete() == null ? 0 : fileRequest.getFullDelete();

            if (fileEntity.getStatus().equals(FileStatus.DELETED) || fullDelete == 1) {
                fileEntityService.deleteFromDisk(fileEntity);
                response = DeleteFileResponse.builder()
                        .fileId(fileRequest.getFileId())
                        .message("File deleted from disk")
                        .build();
            } else {
                fileEntityService.softDeleteFileWithHash(fileEntity);

                response = DeleteFileResponse.builder()
                        .fileId(fileRequest.getFileId())
                        .message("File marked as deleted")
                        .build();
            }

            return new BaseResponse(request.getMsgId(),
                    MessageCode.SUCCESS.getCode(),
                    "File marked as deleted",
                    response);
        } catch (NotFoundAuthToken e) {
            return new BaseResponse(request.getMsgId(),
                    MessageCode.INVALID_TOKEN.getCode(),
                    "Authentication required",
                    null);
        } catch (Exception e) {
            log.error("Error deleting file", e);
            return new BaseResponse(request.getMsgId(),
                    MessageCode.FILE_UPLOAD_ERROR.getCode(),
                    "Failed to delete file: " + e.getMessage(),
                    null);
        }
    }

    private String formatFileSize(long size) {
        if (size < 1024) return size + " B";
        int exp = (int) (Math.log(size) / Math.log(1024));
        String pre = "KMGTPE".charAt(exp-1) + "";
        return String.format("%.1f %sB", size / Math.pow(1024, exp), pre);
    }
}
