package ru.soune.nocopy.service.search;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.DeserializationFeature;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import ru.soune.nocopy.configuration.search.SearchProperties;
import ru.soune.nocopy.dto.BaseResponse;
import ru.soune.nocopy.dto.MessageCode;
import ru.soune.nocopy.dto.file.YandexSearchResponse;
import ru.soune.nocopy.entity.file.FileEntity;
import ru.soune.nocopy.exception.NotValidFieldException;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.SocketTimeoutException;
import java.util.*;
import java.util.concurrent.*;

@Slf4j
@Service

public class SearchImageService {

    private final ObjectMapper objectMapper;

    private final SearchProperties searchProperties;

    public SearchImageService(SearchProperties searchProperties) {
        this.searchProperties = searchProperties;
        this.objectMapper = new ObjectMapper();
    }

    @Value("${yandex.api-key}")
    private String apiKey;

    @Value("${yandex.folder-id}")
    private String folderId;

    @Value("${yandex.search-url}")
    private String searchUrl;

    @Value("${searchapi.api-key:}")
    private String searchApiKey;

    @Value("${server.baseurl}")
    private String appBaseUrl;

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

    public String searchReverseByPublicUrl(FileEntity fileEntity, String engine, String searchType)
            throws IOException, TimeoutException {

        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())
                    )
            );
        }

        String publicUrl = String.format("%s/api/files/public/%s", appBaseUrl, fileEntity.getId());

        log.info("Searching reverse for image: {}", publicUrl);

        try {
            return callReverseImageApiByUrl(publicUrl, engine, searchType);
        } catch (SocketTimeoutException e) {
            log.error("Yandex search timeout after {}", fileEntity.getId());
            throw new TimeoutException("Search timeout");
        }
    }

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

    private OkHttpClient createHttpClient() {
        boolean useProxy = searchProperties.getProxy().isEnabled();
        OkHttpClient.Builder builder = new OkHttpClient.Builder()
                .connectTimeout(35, TimeUnit.SECONDS)
                .writeTimeout(35, TimeUnit.SECONDS)
                .readTimeout(35, TimeUnit.SECONDS)
                .callTimeout(35, TimeUnit.SECONDS)
                .followRedirects(true)
                .followSslRedirects(true)
                .retryOnConnectionFailure(true);

        if (useProxy && searchProperties.getProxy().isEnabled()) {
            Proxy proxy = new Proxy(Proxy.Type.HTTP,
                    new InetSocketAddress(searchProperties.getProxy().getHost(),
                            searchProperties.getProxy().getPort()));
            builder.proxy(proxy);
        }

        return builder.build();
    }

    private String callReverseImageApiByUrl(String imageUrl, String engine, String searchType) throws IOException {
        if (searchApiKey == null || searchApiKey.isBlank()) {
            throw new IllegalStateException("SearchAPI key not configured");
        }

        OkHttpClient client = createHttpClient();

        HttpUrl url = HttpUrl.parse("https://www.searchapi.io/api/v1/search")
                .newBuilder()
                .addQueryParameter("engine", engine)
                .addQueryParameter("api_key", searchApiKey)
                .addQueryParameter("url", imageUrl)
                .addQueryParameter("search_type", searchType)
                .addQueryParameter("t_", String.valueOf(System.currentTimeMillis()))
                .build();

        Request request = new Request.Builder()
                .url(url)
                .header("Accept", "application/json")
                .header("User-Agent", "Mozilla/5.0")
                .build();

        long start = System.currentTimeMillis();

        try (Response response = client.newCall(request).execute()) {
                        ResponseBody body = response.body();
            long duration = System.currentTimeMillis() - start;
            log.info("SearchAPI response code={}, duration={}ms, engine={}",
                    response.code(), duration, engine);

            if (!response.isSuccessful()) {
                String errorBody = response.body() != null ? response.body().string() : "null";
                throw new IOException("API error " + response.code() + ": " + errorBody);
            }

            if (body == null) {
                throw new IOException("Empty response body");
            }

            return body.string();
        }
    }

    public List<YandexSearchResponse.ImageResult> getAllImagesWithoutPagination(String searchApiJson, String findType)
            throws IOException {
        JsonNode root = objectMapper.readTree(searchApiJson);
        JsonNode matches = root.path(findType);

        List<YandexSearchResponse.ImageResult> allImages = new ArrayList<>();

        if (matches.isArray()) {
            for (JsonNode match : matches) {
                YandexSearchResponse.ImageResult result = mapImageResult(match);

                if ("exact_matches".equals(findType)) {
                    JsonNode thumbnail = match.path("thumbnail");
                    if (!thumbnail.isMissingNode()) {
                        String thumbnailUrl = thumbnail.asText();
                        if (thumbnailUrl.startsWith("data:image")) {
                            result.setUrl(thumbnailUrl);
                        } else if (!thumbnailUrl.isBlank()) {
                            result.setUrl(thumbnailUrl);
                        }
                    }

                    JsonNode imageNode = match.path("image");
                    if (!imageNode.isMissingNode()) {
                        String directUrl = imageNode.path("link").asText();
                        if (directUrl != null && !directUrl.isBlank()) {
                            result.setUrl(directUrl);
                        }
                    }
                }

                if (result.getUrl() != null && !result.getUrl().isBlank()) {
                    allImages.add(result);
                }
            }
        }

        return allImages;
    }

    public List<YandexSearchResponse.ImageResult> removeDuplicateUrls(
            List<YandexSearchResponse.ImageResult> yandexImages,
            List<YandexSearchResponse.ImageResult> googleImages) {

        Map<String, YandexSearchResponse.ImageResult> uniqueByUrl = new LinkedHashMap<>();

        for (YandexSearchResponse.ImageResult image : googleImages) {
            String normalizedUrl = normalizeUrl(image.getUrl());
            uniqueByUrl.putIfAbsent(normalizedUrl, image);
        }

        for (YandexSearchResponse.ImageResult image : yandexImages) {
            String normalizedUrl = normalizeUrl(image.getUrl());
            uniqueByUrl.putIfAbsent(normalizedUrl, image);
        }

        return new ArrayList<>(uniqueByUrl.values());
    }

    public List<YandexSearchResponse.ImageResult> paginateResults(
            List<YandexSearchResponse.ImageResult> allResults,
            int page,
            int pageSize) {

        if (allResults.isEmpty()) {
            return new ArrayList<>();
        }

        int totalPages = (int) Math.ceil((double) allResults.size() / pageSize);

        if (page > totalPages) {
            return new ArrayList<>();
        }

        int startIndex = (page - 1) * pageSize;
        int endIndex = Math.min(startIndex + pageSize, allResults.size());

        return new ArrayList<>(allResults.subList(startIndex, endIndex));
    }

    private String normalizeUrl(String url) {
        if (url == null) return null;

        try {
            if (url.startsWith("data:image")) {
                return url.length() > 100 ? url.substring(0, 100) : url;
            }

            java.net.URI uri = new java.net.URI(url);
            String host = uri.getHost();
            if (host != null) {
                host = host.toLowerCase().replaceFirst("^www\\.", "");
            }

            String path = uri.getPath();
            if (path != null && path.length() > 1 && path.endsWith("/")) {
                path = path.substring(0, path.length() - 1);
            }

            return (host != null ? host : "") + (path != null ? path : "");
        } catch (Exception e) {
            return url.toLowerCase()
                    .replaceFirst("^(https?://)?(www\\.)?", "")
                    .replaceFirst("/$", "");
        }
    }

    private YandexSearchResponse.ImageResult mapImageResult(JsonNode match) {

        YandexSearchResponse.ImageResult result =
                new YandexSearchResponse.ImageResult();

        JsonNode imageNode = match.path("image");
        if (imageNode.isObject()) {
            result.setUrl(imageNode.path("link").asText());
            result.setWidth(imageNode.path("width").asInt(0));
            result.setHeight(imageNode.path("height").asInt(0));
        }

        result.setPageUrl(match.path("link").asText());
        result.setPageTitle(match.path("title").asText());

        String source = match.path("source").asText();
        result.setHost(extractHostFromSource(source));

        return result;
    }

    private String extractHostFromSource(String source) {
        if (source == null || source.isEmpty()) {
            return "";
        }
        source = source.replaceFirst("^(https?://)?(www\\.)?", "");

        int slashIndex = source.indexOf('/');
        if (slashIndex > 0) {
            return source.substring(0, slashIndex);
        }

        return source;
    }
}
