package de.avatic.lcc.service.calculation;

import de.avatic.lcc.dto.calculation.create.PremiseSearchResultDTO;
import de.avatic.lcc.model.db.materials.Material;
import de.avatic.lcc.repositories.MaterialRepository;
import de.avatic.lcc.repositories.NodeRepository;
import de.avatic.lcc.repositories.premise.PremiseRepository;
import de.avatic.lcc.repositories.users.UserNodeRepository;
import de.avatic.lcc.service.transformer.generic.MaterialTransformer;
import de.avatic.lcc.service.transformer.generic.NodeTransformer;
import de.avatic.lcc.service.users.AuthorizationService;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Service for analyzing search strings and retrieving materials and their associated suppliers.
 * This service uses repositories and transformers to process and map database entities
 * to DTOs for the result.
 */
@Service
public class PremiseSearchStringAnalyzerService {

    private static final String PART_NUMBER_REGEX_NUMBER_ONLY = "(\\d{4,11})";
    private static final String PART_NUMBER_REGEX = "([a-zA-Z0-9][a-zA-Z0-9\\-]{4,11})";
    private static final Pattern PART_NUMBER_PATTERN = Pattern.compile(PART_NUMBER_REGEX);
    private final MaterialRepository materialRepository;
    private final NodeRepository nodeRepository;
    private final PremiseRepository premiseRepository;
    private final NodeTransformer nodeTransformer;
    private final MaterialTransformer materialTransformer;
    private final UserNodeRepository userNodeRepository;
    private final AuthorizationService authorizationService;

    /**
     * Constructor for the PremiseSearchStringAnalyzerService, initializing the required repositories
     * and transformers for analyzing search strings and processing materials and suppliers.
     *
     * @param materialRepository Repository for accessing material entities from the database.
     * @param nodeRepository Repository for accessing node entities (suppliers) from the database.
     * @param premiseRepository Repository for accessing premise-related data, including suppliers associated with materials.
     * @param nodeTransformer Transformer for mapping database node entities (suppliers) to DTOs.
     * @param materialTransformer Transformer for mapping database material entities to DTOs.
     */
    public PremiseSearchStringAnalyzerService(MaterialRepository materialRepository, NodeRepository nodeRepository, PremiseRepository premiseRepository, NodeTransformer nodeTransformer, MaterialTransformer materialTransformer, UserNodeRepository userNodeRepository, AuthorizationService authorizationService) {
        this.materialRepository = materialRepository;
        this.nodeRepository = nodeRepository;
        this.premiseRepository = premiseRepository;
        this.nodeTransformer = nodeTransformer;
        this.materialTransformer = materialTransformer;
        this.userNodeRepository = userNodeRepository;
        this.authorizationService = authorizationService;
    }

    /**
     * Analyzes the provided search string to find materials and associated suppliers.
     * Processes the search string to extract part numbers, retrieves the associated materials,
     * and identifies the suppliers connected to the materials. These results are returned
     * in the form of a DTO containing lists of materials and suppliers.
     *
     * @param search The search string potentially containing part numbers used to find
     *               materials and their associated suppliers.
     *
     * @return A {@code PremiseSearchResultDTO} containing lists of material DTOs, suppliers,
     *         and user-specific suppliers related to the identified materials.
     */
    public PremiseSearchResultDTO findMaterialAndSuppliers(String search) {

        var userId = authorizationService.getUserId();

        List<Material> material = materialRepository.getByPartNumbers(findPartNumbers(search));
        List<Integer> materialIds = material.stream().map(Material::getId).toList();

        // find suppliers associated with this material.
        List<Integer> supplierIds = premiseRepository.findAssociatedSuppliers(materialIds);
        List<Integer> userSupplierIds = premiseRepository.findAssociatedUserSuppliers(materialIds, userId);

        var dto = new PremiseSearchResultDTO();

        dto.setMaterials(material.stream().map(materialTransformer::toMaterialDTO).toList());
        dto.setSupplier(nodeRepository.getByIds(supplierIds).stream().map(nodeTransformer::toNodeDTO).toList());
        dto.setUserSupplier(userNodeRepository.getByIds(userSupplierIds).stream().map(nodeTransformer::toUserNodeDTO).toList());

        return dto;
    }

    /**
     * Extracts part numbers from the provided search string based on a predefined pattern.
     *
     * @param search The input string that potentially contains part numbers to be extracted.
     * @return A collection of unique part numbers found within the input string.
     */
    public static Collection<String> findPartNumbers(String search) {
        Set<String> partNumbers = new HashSet<>();
        Matcher matcher = PART_NUMBER_PATTERN.matcher(search);

        Matcher numbersOnlyMatcher = Pattern.compile(PART_NUMBER_REGEX_NUMBER_ONLY).matcher(search);

        // Find all matches
        while (matcher.find()) {
            // Get the match from group 1 (inside the lookahead)
            String partNumber = matcher.group(1);
            partNumbers.add(normalizePartNumber(partNumber));
        }

        while (numbersOnlyMatcher.find()) {
            // Get the match from group 1 (inside the lookahead)
            String partNumber = numbersOnlyMatcher.group(1);
            partNumbers.add(normalizePartNumber(partNumber));
        }

        return partNumbers;
    }

    private static String normalizePartNumber(String partNumber) {
        if (partNumber.length() > 12) throw new IllegalArgumentException("Part number must be less than 12 characters");
        return "000000000000".concat(partNumber).substring(partNumber.length());
    }
}
