package com.honey.honey.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.honey.honey.dto.TelegramApiResponse;
import com.honey.honey.dto.TelegramSendResult;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;

import jakarta.annotation.PreDestroy;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

/**
 * Sends requests to Telegram Bot API. No client-side rate limiting; on 429 Too Many Requests
 * from Telegram, schedules a single retry after retry_after seconds.
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TelegramBotApiService {

    private static final int MAX_RETRY_AFTER_SECONDS = 120;

    private final ObjectMapper objectMapper = new ObjectMapper();
    private final RestTemplate restTemplate = new RestTemplate();
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(r -> {
        Thread t = new Thread(r, "telegram-api-retry");
        t.setDaemon(true);
        return t;
    });

    /** Max 429 retries for broadcast (total attempts = 1 + this value). */
    private static final int BROADCAST_429_MAX_RETRIES = 2;

    /**
     * POST for broadcast with 429 retry: on 429, reads retry_after from response, waits that long
     * (capped, with jitter), then retries. Returns final result for audit (success + status code).
     */
    public TelegramSendResult postForBroadcast(String url, HttpEntity<?> entity) {
        int attempt = 0;
        int maxAttempts = 1 + BROADCAST_429_MAX_RETRIES;
        while (true) {
            try {
                ResponseEntity<TelegramApiResponse> response = restTemplate.postForEntity(
                        url, entity, TelegramApiResponse.class);
                int code = response.getStatusCode().value();
                boolean ok = response.getBody() != null && Boolean.TRUE.equals(response.getBody().getOk());
                return new TelegramSendResult(ok && code == 200, code);
            } catch (HttpClientErrorException e) {
                if (e.getStatusCode().value() == 429 && attempt < maxAttempts - 1) {
                    int retryAfterSeconds = parseRetryAfter(e);
                    int waitSeconds = Math.min(Math.max(1, retryAfterSeconds), MAX_RETRY_AFTER_SECONDS);
                    double jitter = 0.9 + (Math.random() * 0.2);
                    long waitMs = (long) (waitSeconds * jitter * 1000);
                    log.warn("Telegram API 429 during broadcast, waiting {} ms then retry (attempt {}/{})", waitMs, attempt + 1, maxAttempts);
                    try {
                        Thread.sleep(waitMs);
                    } catch (InterruptedException ie) {
                        Thread.currentThread().interrupt();
                        return new TelegramSendResult(false, 429);
                    }
                    attempt++;
                    continue;
                }
                return new TelegramSendResult(false, e.getStatusCode().value());
            } catch (HttpServerErrorException e) {
                return new TelegramSendResult(false, e.getStatusCode().value());
            } catch (Exception e) {
                log.debug("Telegram send failed: {}", e.getMessage());
                return new TelegramSendResult(false, 0);
            }
        }
    }

    /**
     * Posts to Telegram API. On 429, schedules a single retry after retry_after seconds.
     *
     * @param url    Telegram API URL
     * @param entity Request entity (must be reusable for retry, e.g. no streams)
     * @return Response body, or null if call failed and was scheduled for retry
     */
    public ResponseEntity<TelegramApiResponse> post(String url, HttpEntity<?> entity) {
        return post(url, entity, 0);
    }

    private ResponseEntity<TelegramApiResponse> post(String url, HttpEntity<?> entity, int retryCount) {
        try {
            ResponseEntity<TelegramApiResponse> response = restTemplate.postForEntity(
                    url,
                    entity,
                    TelegramApiResponse.class
            );
            return response;
        } catch (HttpClientErrorException e) {
            if (e.getStatusCode().value() == 429 && retryCount == 0) {
                int retryAfterSeconds = parseRetryAfter(e);
                if (retryAfterSeconds > 0 && retryAfterSeconds <= MAX_RETRY_AFTER_SECONDS) {
                    log.warn("Telegram API 429 Too Many Requests, scheduling retry in {}s", retryAfterSeconds);
                    scheduler.schedule(() -> {
                        try {
                            post(url, entity, 1);
                        } catch (Exception ex) {
                            log.warn("Telegram API retry failed after 429 – outbound message lost, may affect registration welcome: {}", ex.getMessage());
                            log.error("Telegram API retry failed", ex);
                        }
                    }, retryAfterSeconds, TimeUnit.SECONDS);
                    return null;
                }
            }
            throw e;
        }
    }

    /**
     * Parse retry_after from Telegram 429 response body (parameters.retry_after).
     * Value may be in seconds or milliseconds (if > 120 we treat as ms and convert to seconds).
     */
    private int parseRetryAfter(HttpClientErrorException e) {
        try {
            String body = e.getResponseBodyAsString();
            if (body == null || body.isEmpty()) {
                return 60;
            }
            JsonNode root = objectMapper.readTree(body);
            JsonNode params = root != null ? root.get("parameters") : null;
            if (params != null && params.has("retry_after")) {
                JsonNode ra = params.get("retry_after");
                if (!ra.isNumber()) return 60;
                double value = ra.asDouble();
                if (value > 120) {
                    value = value / 1000.0;
                }
                return (int) Math.ceil(Math.max(1, value));
            }
        } catch (Exception parseEx) {
            log.debug("Could not parse retry_after from 429 response: {}", parseEx.getMessage());
        }
        return 60;
    }

    @PreDestroy
    public void shutdown() {
        scheduler.shutdown();
        try {
            if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
                scheduler.shutdownNow();
            }
        } catch (InterruptedException e) {
            scheduler.shutdownNow();
            Thread.currentThread().interrupt();
        }
    }
}
