/**
 * Copyright (c) 2025 SoftCorporation LLC. All rights reserved.
 *
 * The Software License, Version 1.0
 *
 * SoftCorporation LLC. grants you ("Licensee") a non-exclusive, royalty free,
 * license to use, modify and redistribute this software in source and binary
 * code form, provided that the following conditions are met:
 *
 * 1. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        SoftCorporation LLC. (http://www.softcorporation.com)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 2. This software and SoftCorporation name must not be used to
 *    promote products derived from the software without prior
 *    written permission. For written permission, please contact
 *    info@softcorporation.com.
 *
 * This software is provided "AS IS," without a warranty of any kind.
 * ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
 * IN NO EVENT SHALL THE SOFTCORPORATION BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION).
 *
 */
package com.softcorporation.sandi.search.controller;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import com.softcorporation.sandi.base.model.Client;
import com.softcorporation.sandi.base.service.ClientService;
import com.softcorporation.sandi.base.util.ClientException;
import com.softcorporation.sandi.base.util.Constants;
import com.softcorporation.sandi.search.model.SearchRequest;
import com.softcorporation.sandi.search.model.SearchResponse;
import com.softcorporation.sandi.search.service.SearchService;

@RestController
@RequestMapping("/search")
public class SearchController {

    private static final Logger LOGGER = LoggerFactory.getLogger(SearchController.class);

    @Autowired
    private ClientService clientService;

    @Autowired
    private SearchService searchService;

    @PostMapping
    public ResponseEntity<SearchResponse> searchPost(@RequestBody SearchRequest request) {
        
    	LOGGER.debug("[{}] Received POST request: {}", request.getRequestId(), request);

        ResponseEntity<SearchResponse> response = performSearch(request);
        
        LOGGER.debug("[{}] POST search response: status={}", request.getRequestId(), response.getStatusCode());
        
        return response;
    }

    @GetMapping
    public ResponseEntity<SearchResponse> searchGet(
            @RequestParam String requestId,
            @RequestParam String clientId,
            @RequestParam(required = false) String searchClients,
            @RequestParam(required = false) String searchCollections,
            @RequestParam(required = false) String resultFields,
            @RequestParam(required = false) Integer pageNumber,
            @RequestParam(required = false) Integer pageSize,
            @RequestParam(required = false) String sortFields,
            @RequestParam(required = false) String groupFields,
            @RequestParam(required = false) String facetFields,
            @RequestParam(required = false) String query,
            @RequestParam(required = false) String searchQuery,
            @RequestParam(required = false) String filterQuery,
            @RequestParam(required = false) String highlightFields,
            @RequestParam(required = false) String highlightTags,
            @RequestParam(required = false) String precision,
            @RequestParam(required = false) Boolean exact,
            @RequestParam(required = false) Boolean legacy,
            @RequestParam(required = false) Boolean synonyms,
            @RequestParam(required = false) Boolean dym,
            @RequestParam(required = false) Boolean expand,
            @RequestParam(required = false) Boolean rerank,
            @RequestParam(required = false) Boolean rag) {
        
        LOGGER.debug("[{}] Received GET request: clientId={}", requestId, clientId);
        
        SearchRequest request = new SearchRequest(requestId, clientId);
        request.setSearchClients(searchClients);
        request.setSearchCollections(searchCollections);
        request.setResultFields(resultFields);
        request.setPageNumber(pageNumber);
        request.setPageSize(pageSize);
        request.setSortFields(sortFields);
        request.setGroupFields(groupFields);
        request.setFacetFields(facetFields);
        request.setQuery(query);
        request.setSearchQuery(searchQuery);
        request.setFilterQuery(filterQuery);
        request.setHighlightFields(highlightFields);
        request.setHighlightTags(highlightTags);
        request.setExact(exact);
        request.setPrecision(precision);
        request.setLegacy(legacy);
        request.setSynonyms(synonyms);
        request.setDym(dym);
        request.setExpand(expand);
        request.setRerank(rerank);
        request.setRag(rag);
        
        ResponseEntity<SearchResponse> response = performSearch(request);
        
        LOGGER.debug("[{}] GET search response: status={}", requestId, response.getStatusCode());
        
        return response;
    }
    
    /**
     * Validates search request
     * @param request the request to validate
     * @return SearchResponse with error if validation fails, null if valid
     * @throws ClientException 
     */
    private SearchResponse validateRequest(SearchRequest request) {
        if (request.getRequestId() == null || request.getRequestId().trim().isEmpty()) {
            LOGGER.warn("Search request missing requestId");
            return new SearchResponse("MISSING", "ERROR", "RequestId is required");
        }
        
        Integer pageSize = request.getPageSize();
        if (pageSize == null) {
        	pageSize = 10;
        	request.setPageSize(pageSize);
        } else if (pageSize < 0 || pageSize > Constants.MAX_PAGE_SIZE) {
            LOGGER.warn("[{}] Invalid page size: {}", request.getRequestId(), pageSize);
            return new SearchResponse(request.getRequestId(), "ERROR", "Invalid page size");
        }

        Integer pageNumber = request.getPageNumber();
        if (pageNumber == null) {
        	pageNumber = 1;
        	request.setPageNumber(pageNumber);
        }
        if (pageNumber < 1) {
            LOGGER.warn("[{}] Invalid page number: {}", request.getRequestId(), pageNumber);
            return new SearchResponse(request.getRequestId(), "ERROR", "Invalid page number");
        }
        
        if (request.getClientId() == null || request.getClientId().trim().isEmpty()) {
            LOGGER.warn("[{}] Search request missing clientId", request.getRequestId());
            return new SearchResponse("MISSING", "ERROR", "ClientId is required");
        }
        
        return null;
    }

    /**
     * Validates the Client
     * @param request the request to validate
     * @return SearchResponse with error if validation fails, null if valid
     * @throws ClientException 
     */
    private SearchResponse validateClient(SearchRequest request) throws ClientException {
        LOGGER.debug("[{}] Validating client: {}", request.getRequestId(), request.getClientId());
        Client client = clientService.getClient(request.getClientId());
        if (client == null) {
            LOGGER.warn("[{}] Client not found: {}", request.getRequestId(), request.getClientId());
            return new SearchResponse(request.getRequestId(), "ERROR", "Client not found");
        }
        if (!client.isActive()) {
            LOGGER.warn("[{}] Client is inactive: {}", request.getRequestId(), request.getClientId());
            return new SearchResponse(request.getRequestId(), "ERROR", "Client is inactive");
        }
        request.setClient(client);
        
        return null;
    }

    private ResponseEntity<SearchResponse> performSearch(SearchRequest request) {

        // Validate request
        SearchResponse validationError = validateRequest(request);
        if (validationError != null) {
        	return ResponseEntity.badRequest().body(validationError);
        }

        // Validate client
		try {
			validationError = validateClient(request);
	        if (validationError != null) {
	        	return ResponseEntity.badRequest().body(validationError);
	        }
		} catch (ClientException e) {
            LOGGER.error("[{}] Client: {} Error: {}", request.getRequestId(), request.getClientId(), e.getMessage());
            SearchResponse response = new SearchResponse(request.getRequestId(), "ERROR", "Client error");
            return ResponseEntity.internalServerError().body(response);
		}

        try {
            SearchResponse response = searchService.search(request);
            
            List<?> results = response.getResults();
            LOGGER.info("[{}] Search completed. Documents={}/{}", 
                        request.getRequestId(), results.size(), response.getFoundResults());

            return ResponseEntity.ok(response);

        } catch (Exception e) {
            LOGGER.error("[{}] Search failed: {}", request.getRequestId(), e.getMessage(), e);
            SearchResponse response = new SearchResponse(request.getRequestId(), "ERROR", "Search failed: " + e.getMessage());
            return ResponseEntity.internalServerError().body(response);
        }
    }
    
}