package com.ikon.projectmanagement.service.etl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ikon.connector.dto.response.FieldsConfigDto;
import com.ikon.connector.spi.ConnectorDataSync;
import com.ikon.projectmanagement.entity.FxRateDetailsEntity;
import com.ikon.projectmanagement.entity.FxRateEntity;
import com.ikon.projectmanagement.repository.FxRateRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Component
@RequiredArgsConstructor
@Slf4j
public class FxRateConnector implements ConnectorDataSync {

    private final FxRateRepository fxRateRepository;
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public List<FieldsConfigDto> getFieldsConfig() {
        List<FieldsConfigDto> fieldsConfig = new ArrayList<>();
        fieldsConfig.add(FieldsConfigDto.builder().key("fxRateId").label("FX Rate ID").type("string").build());
        fieldsConfig.add(FieldsConfigDto.builder().key("accountIdentifier").label("Account Identifier").type("string").build());
        fieldsConfig.add(FieldsConfigDto.builder().key("fxRateDetails").label("FX Rate Details").type("object").build());
        return fieldsConfig;
    }

    @Override
    public String getModule() {
        return "FxRate";
    }

    @Override
    @Transactional
    public void syncBatch(List<Map<String, Object>> payload) {
        if (payload == null || payload.isEmpty())
            return;

        List<FxRateEntity> fxRates = payload.stream()
                .map(data -> {
                    UUID fxRateId = parseUUID(data.get("fxRateId"));
                    UUID accountIdentifier = parseUUID(data.get("accountIdentifier"));

                    if (fxRateId == null) {
                        log.warn("Skipping record with null fxRateId");
                        return null;
                    }

                    FxRateEntity fxRate = fxRateRepository.findByIdWithDetails(fxRateId)
                            .orElseGet(() -> FxRateEntity.builder()
                                    .fxRateId(fxRateId)
                                    .build());

                    fxRate.setAccountIdentifier(accountIdentifier);

                    Map<String, FxRateDetailsEntity> detailsMap = buildDetailsMap(data.get("fxRateDetails"));
                    log.info("Built detailsMap size: {}", detailsMap.size());

                    if (!detailsMap.isEmpty()) {
                        fxRate.getFxRateDetails().clear();
                        detailsMap.values().forEach(fxRate::addFxRateDetail);
                        log.info("fxRateDetails after repopulation: {}", fxRate.getFxRateDetails().size());
                    }

                    return fxRate;
                })
                .filter(Objects::nonNull)
                .toList();

        log.info("Saving {} FxRate entities", fxRates.size());
        fxRateRepository.saveAll(fxRates);
    }

    private Map<String, FxRateDetailsEntity> buildDetailsMap(Object rawDetails) {
        Map<String, FxRateDetailsEntity> resultMap = new HashMap<>();

        // Convert to JsonNode — handles ObjectNode, String, or Map
        JsonNode rootNode = toJsonNode(rawDetails);
        if (rootNode == null || rootNode.isNull() || !rootNode.isObject()) {
            log.warn("fxRateDetails could not be parsed as an object, type: {}",
                    rawDetails == null ? "NULL" : rawDetails.getClass().getName());
            return resultMap;
        }

        // Outer loop: year keys e.g. "2026", "2025"
        rootNode.fields().forEachRemaining(yearEntry -> {
            JsonNode currencyMapNode = yearEntry.getValue();
            if (!currencyMapNode.isObject())
                return;

            // Inner loop: currency keys e.g. "USD", "CNY"
            currencyMapNode.fields().forEachRemaining(currencyEntry -> {
                JsonNode fd = currencyEntry.getValue();
                if (!fd.isObject())
                    return;

                // currencyId is null in payload, fallback to currency field
                String currencyId = !fd.path("currencyId").isNull() && fd.hasNonNull("currencyId")
                        ? fd.path("currencyId").asText(null)
                        : fd.path("currency").asText(null);

                if (currencyId == null) {
                    log.warn("Skipping detail with null currencyId and currency for year: {}", yearEntry.getKey());
                    return;
                }

                Boolean active = fd.hasNonNull("activeStatus")
                        ? fd.path("activeStatus").asBoolean(true)
                        : Boolean.TRUE;

                FxRateDetailsEntity detail = FxRateDetailsEntity.builder()
                        .currencyId(currencyId)
                        .currency(fd.path("currency").asText(null))
                        .fxRate(fd.hasNonNull("fxRate") ? fd.path("fxRate").asDouble() : null)
                        .activeStatus(active)
                        .year(fd.path("year").asText(null))
                        .build();

                resultMap.put(currencyId, detail);
                log.debug("Added detail: currencyId={}, fxRate={}, year={}",
                        currencyId, detail.getFxRate(), detail.getYear());
            });
        });

        return resultMap;
    }

    /**
     * Converts ObjectNode, Map, or JSON String → JsonNode
     */
    private JsonNode toJsonNode(Object rawDetails) {
        if (rawDetails == null)
            return null;

        // Already a JsonNode (e.g. ObjectNode from Jackson)
        if (rawDetails instanceof JsonNode) {
            return (JsonNode) rawDetails;
        }

        // Plain JSON String
        if (rawDetails instanceof String) {
            try {
                return objectMapper.readTree((String) rawDetails);
            } catch (Exception e) {
                log.error("Failed to parse fxRateDetails JSON string: {}", e.getMessage());
                return null;
            }
        }

        // Plain Map (e.g. LinkedHashMap)
        if (rawDetails instanceof Map) {
            return objectMapper.valueToTree(rawDetails);
        }

        log.warn("Unrecognized fxRateDetails type: {}", rawDetails.getClass().getName());
        return null;
    }

    // --- Utility Helpers ---

    private UUID parseUUID(Object value) {
        if (value == null)
            return null;
        try {
            return UUID.fromString(value.toString());
        } catch (Exception e) {
            log.warn("Failed to parse UUID from value: {}", value);
            return null;
        }
    }
}