package com.honey.honey.service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.honey.honey.dto.CryptoDepositMethodsResponse;
import com.honey.honey.dto.DepositAddressApiRequest;
import com.honey.honey.dto.DepositAddressResponse;
import com.honey.honey.dto.DepositMethodsDto;
import com.honey.honey.model.CryptoDepositConfig;
import com.honey.honey.model.CryptoDepositMethod;
import com.honey.honey.repository.CryptoDepositConfigRepository;
import com.honey.honey.repository.CryptoDepositMethodRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
@Slf4j
public class CryptoDepositService {

    private static final String DEPOSIT_METHODS_PATH = "api/v1/deposit-methods";
    private static final String DEPOSIT_ADDRESS_PATH = "api/v1/deposit-address";

    @Value("${app.crypto-api.base-url:https://spin-passim.tech/}")
    private String baseUrl;

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

    private final RestTemplate restTemplate = new RestTemplate();
    private final ObjectMapper objectMapper;
    private final CryptoDepositConfigRepository configRepository;
    private final CryptoDepositMethodRepository methodRepository;

    /**
     * Returns minimum deposit from DB only (no external API call).
     * Used by Store screen for validation.
     */
    @Transactional(readOnly = true)
    public BigDecimal getMinimumDeposit() {
        return configRepository.findById(1)
                .map(CryptoDepositConfig::getMinimumDeposit)
                .orElse(BigDecimal.valueOf(2.5));
    }

    /**
     * Returns deposit methods and minimum deposit from DB only (no external API call).
     * Used by Payment Options screen.
     */
    @Transactional(readOnly = true)
    public DepositMethodsDto getDepositMethodsFromDb() {
        Optional<CryptoDepositConfig> configOpt = configRepository.findById(1);
        BigDecimal minDeposit = configOpt.map(CryptoDepositConfig::getMinimumDeposit).orElse(BigDecimal.valueOf(2.5));
        List<CryptoDepositMethod> methods = methodRepository.findAll();
        return toDto(minDeposit, methods);
    }

    /**
     * Runs every 10 minutes. Fetches from external API; if response hash differs from stored hash, updates methods and config.
     */
    @Scheduled(cron = "0 */10 * * * ?")
    @Transactional
    public void syncFromExternalIfHashChanged() {
        String url = baseUrl.endsWith("/") ? baseUrl + DEPOSIT_METHODS_PATH : baseUrl + "/" + DEPOSIT_METHODS_PATH;
        HttpHeaders headers = new HttpHeaders();
        if (apiKey != null && !apiKey.isEmpty()) {
            headers.set("API-KEY", apiKey);
        }
        HttpEntity<Void> entity = new HttpEntity<>(headers);

        ResponseEntity<CryptoDepositMethodsResponse> response;
        try {
            if (log.isDebugEnabled()) {
                log.debug("Crypto API request: GET {} (sync deposit methods)", url);
            }
            response = restTemplate.exchange(url, HttpMethod.GET, entity, CryptoDepositMethodsResponse.class);
            if (log.isDebugEnabled()) {
                log.debug("Crypto API response: GET {} status={} body={}", url, response.getStatusCode(), toJson(response.getBody()));
            }
        } catch (HttpStatusCodeException e) {
            if (log.isDebugEnabled()) {
                log.debug("Crypto API error response: GET {} status={} body={}", url, e.getStatusCode(), e.getResponseBodyAsString());
            }
            log.warn("Crypto deposit sync (10 min): failed to fetch from external API: {}", e.getMessage());
            return;
        } catch (Exception e) {
            log.warn("Crypto deposit sync (10 min): failed to fetch from external API: {}", e.getMessage());
            return;
        }

        CryptoDepositMethodsResponse body = response.getBody();
        if (body == null || body.getResult() == null || body.getResult().getActiveMethods() == null) {
            return;
        }
        if (body.getRequestInfo() != null && body.getRequestInfo().getErrorCode() != null && body.getRequestInfo().getErrorCode() != 0) {
            log.warn("Crypto deposit sync (10 min): API error: {}", body.getRequestInfo().getErrorMessage());
            return;
        }

        String responseHash = body.getResult().getHash();
        String storedHash = configRepository.findById(1).map(CryptoDepositConfig::getMethodsHash).orElse(null);
        if (responseHash != null && !responseHash.isEmpty() && responseHash.equals(storedHash)) {
            log.debug("Crypto deposit sync (10 min): hash unchanged, skip update");
            return;
        }

        List<CryptoDepositMethodsResponse.ActiveMethod> active = body.getResult().getActiveMethods();
        double minSum = active.stream()
                .mapToDouble(m -> m.getMinDepositSum() != null ? m.getMinDepositSum() : 0)
                .filter(v -> v > 0)
                .min()
                .orElse(2.5);

        methodRepository.deleteAllInBatch();
        Instant now = Instant.now();
        for (CryptoDepositMethodsResponse.ActiveMethod m : active) {
            if (m.getPid() == null) continue;
            CryptoDepositMethod method = CryptoDepositMethod.builder()
                    .pid(m.getPid())
                    .name(m.getName() != null ? m.getName() : "")
                    .network(m.getNetwork() != null ? m.getNetwork() : "")
                    .example(m.getExample())
                    .minDepositSum(BigDecimal.valueOf(m.getMinDepositSum() != null ? m.getMinDepositSum() : 2.5).setScale(2, RoundingMode.HALF_UP))
                    .updatedAt(now)
                    .build();
            methodRepository.save(method);
        }

        CryptoDepositConfig config = configRepository.findById(1).orElseGet(() -> {
            CryptoDepositConfig c = new CryptoDepositConfig();
            c.setId(1);
            return c;
        });
        config.setMinimumDeposit(BigDecimal.valueOf(minSum).setScale(2, RoundingMode.HALF_UP));
        config.setMethodsHash(responseHash);
        config.setUpdatedAt(now);
        configRepository.save(config);

        log.info("Crypto deposit sync (10 min): updated {} methods, minimum_deposit={}, hash={}", active.size(), minSum, responseHash);
    }

    /**
     * Calls external API POST deposit-address to get wallet address and amount in coins.
     * Does not create any payment record; caller is responsible for that.
     */
    public DepositAddressResponse postDepositAddress(DepositAddressApiRequest request) {
        Integer userId = request.getUserData() != null ? request.getUserData().getInternalId() : null;
        String url = baseUrl.endsWith("/") ? baseUrl + DEPOSIT_ADDRESS_PATH : baseUrl + "/" + DEPOSIT_ADDRESS_PATH;
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        if (apiKey != null && !apiKey.isEmpty()) {
            headers.set("API-KEY", apiKey);
        }
        HttpEntity<DepositAddressApiRequest> entity = new HttpEntity<>(request, headers);
        if (log.isDebugEnabled()) {
            log.debug("Crypto API request: userId={} POST {} body={}", userId, url, toJson(request));
        }
        try {
            ResponseEntity<DepositAddressResponse> response = restTemplate.exchange(
                    url, HttpMethod.POST, entity, DepositAddressResponse.class);
            DepositAddressResponse responseBody = response.getBody();
            if (log.isDebugEnabled()) {
                log.debug("Crypto API response: userId={} POST {} status={} body={}", userId, url, response.getStatusCode(), toJson(responseBody));
            }
            return responseBody;
        } catch (HttpStatusCodeException e) {
            if (log.isDebugEnabled()) {
                log.debug("Crypto API error response: userId={} POST {} status={} body={}", userId, url, e.getStatusCode(), e.getResponseBodyAsString());
            }
            throw e;
        }
    }

    private String toJson(Object o) {
        if (o == null) return "null";
        try {
            return objectMapper.writeValueAsString(o);
        } catch (JsonProcessingException e) {
            return "<" + e.getMessage() + ">";
        }
    }

    private static DepositMethodsDto toDto(BigDecimal minimumDeposit, List<CryptoDepositMethod> methods) {
        List<DepositMethodsDto.DepositMethodItemDto> list = methods.stream()
                .map(m -> DepositMethodsDto.DepositMethodItemDto.builder()
                        .pid(m.getPid())
                        .name(m.getName())
                        .network(m.getNetwork())
                        .example(m.getExample())
                        .minDepositSum(m.getMinDepositSum())
                        .build())
                .collect(Collectors.toList());
        return DepositMethodsDto.builder()
                .minimumDeposit(minimumDeposit)
                .activeMethods(list)
                .build();
    }
}
