package ru.shine.promo.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.p2p.solanaj.core.Account;
import org.p2p.solanaj.core.PublicKey;
import org.p2p.solanaj.core.Transaction;
import org.p2p.solanaj.programs.SystemProgram;
import org.p2p.solanaj.rpc.RpcClient;
import org.p2p.solanaj.rpc.RpcException;
import org.p2p.solanaj.rpc.types.config.Commitment;
import org.p2p.solanaj.rpc.types.config.RpcSendTransactionConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import ru.shine.promo.config.AppProperties;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Locale;

@Service
public class PromoTransferService {

    private static final Logger log = LoggerFactory.getLogger(PromoTransferService.class);

    private final AppProperties appProperties;
    private final ObjectMapper objectMapper = new ObjectMapper();

    public PromoTransferService(AppProperties appProperties) {
        this.appProperties = appProperties;
    }

    public boolean isSolanaWalletValid(String wallet) {
        if (wallet == null || wallet.isBlank()) {
            return false;
        }
        try {
            PublicKey publicKey = new PublicKey(wallet.trim());
            return publicKey.toByteArray().length == PublicKey.PUBLIC_KEY_LENGTH;
        } catch (Exception e) {
            return false;
        }
    }

    public String sendPromoTransfer(String recipientWallet) {
        PublicKey recipient = parseWalletOrThrow(recipientWallet);
        Account sender = readSenderAccount();
        long lamports = appProperties.getPromoTransferAmountLamports();

        RpcClient rpcClient = new RpcClient(appProperties.getSolanaRpcUrl());

        try {
            long senderBalance = rpcClient.getApi().getBalance(sender.getPublicKey(), Commitment.CONFIRMED);
            if (senderBalance < lamports) {
                throw new PromoException(HttpStatus.BAD_REQUEST, "Недостаточно средств на devnet-кошельке отправителя");
            }

            Transaction transaction = new Transaction()
                    .addInstruction(SystemProgram.transfer(sender.getPublicKey(), recipient, lamports));

            RpcSendTransactionConfig config = RpcSendTransactionConfig.builder()
                    .encoding(RpcSendTransactionConfig.Encoding.base64)
                    .skipPreFlight(false)
                    .maxRetries(3)
                    .build();

            return rpcClient.getApi().sendTransaction(
                    transaction,
                    Collections.singletonList(sender),
                    null,
                    config
            );
        } catch (PromoException e) {
            throw e;
        } catch (RpcException e) {
            String message = e.getMessage() == null ? "" : e.getMessage().toLowerCase(Locale.ROOT);
            if (message.contains("insufficient")) {
                throw new PromoException(HttpStatus.BAD_REQUEST, "Недостаточно средств на devnet-кошельке отправителя", e);
            }
            if (message.contains("rpc")) {
                throw new PromoException(HttpStatus.BAD_GATEWAY, "Ошибка RPC Solana", e);
            }
            log.error("Solana transfer failed: {}", e.getMessage(), e);
            throw new PromoException(HttpStatus.BAD_GATEWAY, "Ошибка отправки транзакции", e);
        } catch (Exception e) {
            log.error("Unexpected Solana transfer error: {}", e.getMessage(), e);
            throw new PromoException(HttpStatus.BAD_GATEWAY, "Ошибка отправки транзакции", e);
        }
    }

    public String buildExplorerUrl(String signature) {
        return String.format(appProperties.getPromoExplorerTxUrlTemplate(), signature);
    }

    private PublicKey parseWalletOrThrow(String wallet) {
        if (!isSolanaWalletValid(wallet)) {
            throw new PromoException(HttpStatus.BAD_REQUEST, "Неверный формат Solana-адреса");
        }
        return new PublicKey(wallet.trim());
    }

    private Account readSenderAccount() {
        Path keypairFile = Path.of(appProperties.getSolanaSenderKeypairFile());
        if (!Files.exists(keypairFile)) {
            throw new PromoException(HttpStatus.INTERNAL_SERVER_ERROR, "Файл keypair отправителя не найден");
        }

        try {
            int[] keyArray = objectMapper.readValue(Files.readString(keypairFile), int[].class);
            if (keyArray.length != 64) {
                throw new PromoException(
                        HttpStatus.INTERNAL_SERVER_ERROR,
                        "Некорректный формат keypair отправителя: ожидается массив из 64 чисел"
                );
            }

            byte[] secret = new byte[64];
            for (int i = 0; i < keyArray.length; i++) {
                int value = keyArray[i];
                if (value < 0 || value > 255) {
                    throw new PromoException(
                            HttpStatus.INTERNAL_SERVER_ERROR,
                            "Некорректный формат keypair отправителя: числа должны быть в диапазоне 0..255"
                    );
                }
                secret[i] = (byte) value;
            }

            return new Account(secret);
        } catch (PromoException e) {
            throw e;
        } catch (IOException e) {
            throw new PromoException(HttpStatus.INTERNAL_SERVER_ERROR, "Ошибка чтения keypair отправителя", e);
        }
    }
}
