package ru.nbch.indicsearchservice.util;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.ibatis.cursor.Cursor;
import org.springframework.stereotype.Component;
import ru.nbch.indicsearchservice.config.settings.ApplicationSettings;
import ru.nbch.indicsearchservice.model.LocalizationObject;
import ru.nbch.indicsearchservice.model.TableMetaData;
import ru.nbch.indicsearchservice.repository.bank.LocalizationObjectMapper;

@Component
public class IndicSearchUtils {
    private static final Set<String> REQUIRED_FIELDS = Set.of(
        "FID",
        "SERIAL_NUM",
        "ACC_SERIAL_NUM",
        "PARENT_SERIAL_NUM"
    );

    private final ApplicationSettings settings;
    private final LocalizationObjectMapper localizationObjectMapper;

    public IndicSearchUtils(ApplicationSettings settings,
                            LocalizationObjectMapper localizationObjectMapper) {
        this.settings = settings;
        this.localizationObjectMapper = localizationObjectMapper;
    }

    public Path writeRowsToFile(UUID taskId,
                                String tableName,
                                Cursor<LinkedHashMap<String, Object>> databaseCursor,
                                Map<String, TableMetaData> tableMetaDataMap) {
        return writeRowsToFile(taskId, tableName, databaseCursor, null, tableMetaDataMap);
    }

    public Path writeRowsToFile(UUID taskId,
                                String tableName,
                                Cursor<LinkedHashMap<String, Object>> databaseCursor,
                                Set<Integer> foundFids,
                                Map<String, TableMetaData> tableMetaDataMap) {

        Path resultFile = settings.getStorageDirectory().toPath().resolve(tableName + "_" + taskId + ".csv");

        TableMetaData tableMetaData = tableMetaDataMap.get(tableName);
        Set<String> configuredFields = tableMetaData != null ?
            tableMetaData.getFieldMetaData().keySet() :
            new HashSet<>();

        try (BufferedWriter writer = Files.newBufferedWriter(resultFile, StandardOpenOption.CREATE, StandardOpenOption.APPEND)) {
            for (LinkedHashMap<String, Object> row : databaseCursor) {
                if (foundFids != null) {
                    Object fidValue = row.get("FID");
                    if (fidValue != null) {
                        foundFids.add(Integer.parseInt(fidValue.toString()));
                    }
                }

                StringBuilder sb = new StringBuilder();
                sb.append(tableName).append("%%%");

                for (Map.Entry<String, Object> entry : row.entrySet()) {
                    String columnName = entry.getKey();
                    String columnNameUpper = columnName.toUpperCase();

                    if (configuredFields.contains(columnName) || REQUIRED_FIELDS.contains(columnNameUpper)) {
                        sb.append(columnNameUpper).append("%%%");

                        Object columnValue = entry.getValue();
                        if (columnValue != null) {
                            sb.append(columnValue).append("%%%");
                        } else {
                            sb.append("%%%");
                        }
                    }
                }

                if (sb.length() >= 3) {
                    sb.setLength(sb.length() - 3);
                }

                writer.write(sb.toString());
                writer.newLine();
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return resultFile;
    }

    public Map<String, Optional<String>> getOptionalLocalizationStrings(List<String> localizationCodes) {
        if (localizationCodes == null || localizationCodes.isEmpty()) {
            return Map.of();
        }

        List<LocalizationObject> foundValues = localizationObjectMapper.findAllByCodes(localizationCodes);
        if (foundValues == null || foundValues.isEmpty()) {
            return Map.of();
        }

        return foundValues.stream().collect(Collectors.toMap(LocalizationObject::getCode, s -> Optional.ofNullable(s.getText())));
    }
}
