package ru.soune.nocopy.service.search;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.cloud.vision.v1.*;
import com.google.protobuf.ByteString;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.GoogleVisionSearchResponse;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.exception.NotValidFieldException;
import ru.soune.nocopy.repository.FileEntityRepository;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class GoogleVisionSearchService {
    private final FileEntityRepository fileEntityRepository;

    private final ObjectMapper objectMapper;

    private ImageAnnotatorClient visionClient;

    @PostConstruct
    public void init() throws IOException {
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

        initVisionClient();
    }

    private void initVisionClient() throws IOException {
        try (InputStream credentialsStream = new ClassPathResource("config/google-service-account.json").getInputStream()) {
            GoogleCredentials credentials = GoogleCredentials.fromStream(credentialsStream);

            ImageAnnotatorSettings settings = ImageAnnotatorSettings.newBuilder()
                    .setCredentialsProvider(() -> credentials)
                    .build();

            this.visionClient = ImageAnnotatorClient.create(settings);
        }
    }

    public GoogleVisionSearchResponse searchByFileEntity(String fileId) throws IOException {
        FileEntity fileEntity = fileEntityRepository.findById(fileId)
                .orElseThrow(() -> {
                    throw new NotValidFieldException("File not found", new BaseResponse(20007,
                            MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getDescription(),
                            Map.of("fileId", fileId)));
                });

        if (!isImageFile(fileEntity)) {
            throw new NotValidFieldException("File not image", new BaseResponse(20007,
                    MessageCode.INVALID_FIELD.getCode(), MessageCode.INVALID_FIELD.getDescription(),
                    Map.of("file_type", fileEntity.getMimeType())));
        }

        byte[] fileBytes;
        try {
            fileBytes = readFileFromDisk(fileEntity);
        } catch (IOException e) {
            throw new NotValidFieldException("File not found or cannot read file", new BaseResponse(20007,
                    MessageCode.FILE_NOT_FOUND.getCode(), MessageCode.FILE_NOT_FOUND.getDescription(),
                    Map.of("fileId", fileId,
                            "filePath", fileEntity.getFilePath())));
        }

        return callGoogleVisionApi(fileBytes);
    }

    private boolean isImageFile(FileEntity fileEntity) {
        String mimeType = fileEntity.getMimeType();
        return mimeType != null && mimeType.startsWith("image");
    }

    private byte[] readFileFromDisk(FileEntity fileEntity) throws IOException {
        Path filePath = Path.of(fileEntity.getFilePath());

        if (!Files.exists(filePath) || !Files.isReadable(filePath)) {
            throw new IOException("Cannot read file: " + fileEntity.getFilePath());
        }

        return Files.readAllBytes(filePath);
    }

    private GoogleVisionSearchResponse callGoogleVisionApi(byte[] imageBytes) throws IOException {
        ByteString imgBytes = ByteString.copyFrom(imageBytes);
        Image image = Image.newBuilder()
                .setContent(imgBytes)
                .build();

        Feature feature = Feature.newBuilder()
                .setType(Feature.Type.WEB_DETECTION)
                .setMaxResults(20)
                .build();

        AnnotateImageRequest request = AnnotateImageRequest.newBuilder()
                .addFeatures(feature)
                .setImage(image)
                .build();

        BatchAnnotateImagesResponse response = visionClient.batchAnnotateImages(List.of(request));

        if (response.getResponsesCount() == 0) {
            throw new IOException("Empty response from Google Vision API");
        }

        AnnotateImageResponse singleResponse = response.getResponses(0);

        if (singleResponse.hasError()) {
            throw new IOException("Google Vision API error: " + singleResponse.getError().getMessage());
        }

        return convertToResponse(singleResponse.getWebDetection());
    }

    private GoogleVisionSearchResponse convertToResponse(WebDetection webDetection) {
        GoogleVisionSearchResponse response = new GoogleVisionSearchResponse();

        if (webDetection.getBestGuessLabelsCount() > 0) {
            response.setBestGuessLabels(
                    webDetection.getBestGuessLabelsList().stream()
                            .map(label -> {
                                GoogleVisionSearchResponse.BestGuessLabel dtoLabel =
                                        new GoogleVisionSearchResponse.BestGuessLabel();
                                dtoLabel.setLabel(label.getLabel());
                                dtoLabel.setLanguageCode(label.getLanguageCode());
                                return dtoLabel;
                            })
                            .toList()
            );
        }

        if (webDetection.getFullMatchingImagesCount() > 0) {
            response.setFullMatchingImages(
                    webDetection.getFullMatchingImagesList().stream()
                            .map(img -> {
                                GoogleVisionSearchResponse.ImageResult dtoImg =
                                        new GoogleVisionSearchResponse.ImageResult();
                                dtoImg.setUrl(img.getUrl());
                                dtoImg.setScore(img.getScore());
                                return dtoImg;
                            })
                            .toList()
            );
        }

        if (webDetection.getVisuallySimilarImagesCount() > 0) {
            response.setVisuallySimilarImages(
                    webDetection.getVisuallySimilarImagesList().stream()
                            .map(img -> {
                                GoogleVisionSearchResponse.ImageResult dtoImg =
                                        new GoogleVisionSearchResponse.ImageResult();
                                dtoImg.setUrl(img.getUrl());
                                dtoImg.setScore(img.getScore());
                                return dtoImg;
                            })
                            .toList()
            );
        }

        if (webDetection.getPagesWithMatchingImagesCount() > 0) {
            response.setPagesWithMatchingImages(
                    webDetection.getPagesWithMatchingImagesList().stream()
                            .map(page -> {
                                GoogleVisionSearchResponse.PageResult dtoPage =
                                        new GoogleVisionSearchResponse.PageResult();
                                dtoPage.setUrl(page.getUrl());
                                dtoPage.setPageTitle(page.getPageTitle());
                                return dtoPage;
                            })
                            .toList()
            );
        }

        if (webDetection.getPartialMatchingImagesCount() > 0) {
            response.setPartialMatchingImages(
                    webDetection.getPartialMatchingImagesList().stream()
                            .map(img -> {
                                GoogleVisionSearchResponse.ImageResult dtoImg =
                                        new GoogleVisionSearchResponse.ImageResult();
                                dtoImg.setUrl(img.getUrl());
                                dtoImg.setScore(img.getScore());
                                return dtoImg;
                            })
                            .toList()
            );
        }

        return response;
    }
}