package com.honey.honey.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.honey.honey.config.TelegramProperties;
import com.honey.honey.exception.UnauthorizedException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

import static java.nio.charset.StandardCharsets.UTF_8;

@Slf4j
@Service
@RequiredArgsConstructor
public class TelegramAuthService {

    private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
    private static final String WEB_APP_DATA_CONSTANT = "WebAppData";

    private final TelegramProperties telegramProperties;
    private final LocalizationService localizationService;
    private final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * Validates and parses Telegram initData string.
     * Returns a map containing:
     * - "user": parsed user data (Map)
     * Note: Referral handling is done via bot, not through WebApp initData.
     */
    public Map<String, Object> validateAndParseInitData(String initData) {

        if (initData == null || initData.isBlank()) {
            throw new UnauthorizedException(localizationService.getMessage("auth.error.initDataMissing"));
        }

        try {
            // Step 1. Parse query string into key/value pairs.
            Map<String, String> parsedData = parseQueryString(initData);

            String receivedHash = parsedData.remove("hash");
            if (receivedHash == null) {
                throw new UnauthorizedException(localizationService.getMessage("auth.error.missingHash"));
            }

            // Step 2. Build data check string.
            String dataCheckString = createDataCheckString(parsedData);

            // Step 3. Derive secret key based on Telegram WebApp rules.
            byte[] secretKey = deriveSecretKey(telegramProperties.getBotToken());

            // Step 4. Calculate our own hash and compare.
            String calculatedHash = calculateHmacSha256(dataCheckString, secretKey);

            if (!receivedHash.equals(calculatedHash)) {
                log.warn("Telegram signature mismatch. Expected={}, Received={}", calculatedHash, receivedHash);
                throw new UnauthorizedException(localizationService.getMessage("auth.error.invalidSignature"));
            }

            // Step 5. Extract the user JSON from initData.
            Map<String, String> decoded = decodeQueryParams(initData);
            String userJson = decoded.get("user");

            if (userJson == null) {
                throw new UnauthorizedException(localizationService.getMessage("auth.error.userFieldMissing"));
            }

            // Step 6. Parse JSON into map.
            // Note: Referral handling is done via bot registration endpoint, not through WebApp initData.
            Map<String, Object> result = new HashMap<>();
            result.put("user", objectMapper.readValue(userJson, Map.class));
            // "start" parameter is not included for WebApp - referrals handled by bot
            
            return result;

        } catch (UnauthorizedException ex) {
            throw ex;

        } catch (Exception ex) {
            log.error("Telegram initData validation failed: {}", ex.getMessage(), ex);
            throw new UnauthorizedException(localizationService.getMessage("auth.error.invalidInitData"));
        }
    }

    // -------------------------------------------
    // Internal helper methods
    // -------------------------------------------

    private Map<String, String> parseQueryString(String queryString) {
        Map<String, String> params = new HashMap<>();

        try {
            String[] pairs = queryString.split("&");

            for (String pair : pairs) {
                int idx = pair.indexOf("=");

                if (idx <= 0) continue;

                String key = URLDecoder.decode(pair.substring(0, idx), UTF_8);
                String value = URLDecoder.decode(pair.substring(idx + 1), UTF_8);

                params.put(key, value);
            }
        } catch (Exception ex) {
            log.warn("Failed to parse initData query string: {}", ex.getMessage());
        }

        return params;
    }

    private String createDataCheckString(Map<String, String> data) {
        List<String> sortedKeys = new ArrayList<>(data.keySet());
        Collections.sort(sortedKeys);

        StringBuilder sb = new StringBuilder();

        for (String key : sortedKeys) {
            if (sb.length() > 0) sb.append("\n");
            sb.append(key).append("=").append(data.get(key));
        }

        return sb.toString();
    }

    private byte[] deriveSecretKey(String botToken) throws NoSuchAlgorithmException, InvalidKeyException {
        Mac hmacSha256 = Mac.getInstance(HMAC_SHA256_ALGORITHM);

        // Telegram requires using "WebAppData" as the key for deriving secret
        SecretKeySpec secretKeySpec =
                new SecretKeySpec(WEB_APP_DATA_CONSTANT.getBytes(StandardCharsets.UTF_8), HMAC_SHA256_ALGORITHM);

        hmacSha256.init(secretKeySpec);

        return hmacSha256.doFinal(botToken.getBytes(StandardCharsets.UTF_8));
    }

    private String calculateHmacSha256(String data, byte[] key)
            throws NoSuchAlgorithmException, InvalidKeyException {

        Mac hmacSha256 = Mac.getInstance(HMAC_SHA256_ALGORITHM);
        SecretKeySpec secretKeySpec = new SecretKeySpec(key, HMAC_SHA256_ALGORITHM);

        hmacSha256.init(secretKeySpec);

        byte[] hashBytes = hmacSha256.doFinal(data.getBytes(StandardCharsets.UTF_8));

        return bytesToHex(hashBytes);
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder(bytes.length * 2);

        for (byte b : bytes) {
            String hex = Integer.toHexString(0xff & b);

            if (hex.length() == 1) hexString.append('0');

            hexString.append(hex);
        }

        return hexString.toString();
    }

    private Map<String, String> decodeQueryParams(String qs) {
        Map<String, String> map = new HashMap<>();

        for (String part : qs.split("&")) {
            int idx = part.indexOf('=');

            if (idx > 0) {
                String key = URLDecoder.decode(part.substring(0, idx), UTF_8);
                String val = URLDecoder.decode(part.substring(idx + 1), UTF_8);
                map.put(key, val);
            }
        }

        return map;
    }
}

