package camel_oil_api

import (
	"bytes"
	"crypto/md5"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/pem"
	"errors"
	"strings"

	"github.com/duke-git/lancet/v2/convertor"
)

// EncryptWithPublicKey encrypts the input JSON string using RSA public key with block processing for large data.
// It uses PKCS1Padding and supports chunking based on key size minus 11 bytes for padding.
func EncryptWithPublicKey(base64PublicKey, jsonString string) (string, error) {
	publicKey, err := parsePublicKey(base64PublicKey)
	if err != nil {
		return "", err
	}

	data := []byte(jsonString)

	// Calculate maximum plaintext block size: modulus size - 11 bytes for PKCS1Padding
	modulusSizeBytes := publicKey.N.BitLen() / 8
	maxBlockSize := modulusSizeBytes - 11

	var buf bytes.Buffer

	offset := 0
	for offset < len(data) {
		end := min(offset+maxBlockSize, len(data))

		encryptedBlock, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, data[offset:end])
		if err != nil {
			return "", err
		}

		// Each encrypted block is always the size of the modulus
		buf.Write(encryptedBlock)
		offset = end
	}

	return convertor.ToStdBase64(buf.Bytes()), nil
}

// DecryptWithPrivateKey decrypts the base64-encoded ciphertext using RSA private key with block processing for large data.
// It uses PKCS1Padding and follows the Java implementation: Math.min(blockSize, data.length - offset)
func DecryptWithPrivateKey(base64PrivateKey, ciphertext string) (string, error) {
	privateKey, err := parsePrivateKey(base64PrivateKey)
	if err != nil {
		return "", err
	}

	// Decode base64-encoded ciphertext
	cipherData, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		return "", err
	}

	// RSA modulus size: each encrypted block is exactly this size
	blockSize := privateKey.N.BitLen() / 8 // 1024-bit key = 128 bytes

	var buf bytes.Buffer

	offset := 0
	for offset < len(cipherData) {
		// Calculate current block size: min(blockSize, remaining data)
		currentBlockSize := blockSize
		if currentBlockSize > len(cipherData)-offset {
			currentBlockSize = len(cipherData) - offset
		}

		decryptedBlock, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherData[offset:offset+currentBlockSize])
		if err != nil {
			return "", err
		}

		buf.Write(decryptedBlock)
		offset += currentBlockSize
	}

	return buf.String(), nil
}

// DecryptWithPrivateThenEncryptWithPublic decrypts ciphertext with private key,
// then encrypts the result with public key. Commonly used for authorization token transformation.
func DecryptWithPrivateThenEncryptWithPublic(base64PrivateKey, base64PublicKey, ciphertext string) (string, error) {
	privateKey, err := parsePrivateKey(base64PrivateKey)
	if err != nil {
		return "", err
	}

	publicKey, err := parsePublicKey(base64PublicKey)
	if err != nil {
		return "", err
	}

	// Decrypt ciphertext using private key
	cipherData, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		return "", err
	}

	plaintext, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherData)
	if err != nil {
		return "", err
	}

	// Encrypt plaintext using public key
	encrypted, err := rsa.EncryptPKCS1v15(rand.Reader, publicKey, plaintext)
	if err != nil {
		return "", err
	}

	return convertor.ToStdBase64(encrypted), nil
}

// parsePrivateKey parses a private key from base64 or PEM format.
// Supports both base64-encoded PKCS#8 keys and PEM-wrapped keys.
func parsePrivateKey(keyStr string) (*rsa.PrivateKey, error) {
	var keyBytes []byte
	var err error

	// Decode base64 if not PEM format
	if strings.Contains(keyStr, "-----BEGIN") {
		keyBytes = []byte(keyStr)
	} else {
		keyBytes, err = base64.StdEncoding.DecodeString(keyStr)
		if err != nil {
			return nil, err
		}
	}

	// Extract key data from PEM block if present
	if strings.Contains(keyStr, "-----BEGIN") {
		block, _ := pem.Decode(keyBytes)
		if block == nil {
			return nil, errors.New("failed to parse PEM block")
		}
		keyBytes = block.Bytes
	}

	// Parse PKCS#8 private key
	parsedKey, err := x509.ParsePKCS8PrivateKey(keyBytes)
	if err != nil {
		return nil, err
	}

	privateKey, ok := parsedKey.(*rsa.PrivateKey)
	if !ok {
		return nil, errors.New("not an RSA private key")
	}

	return privateKey, nil
}

// parsePublicKey parses a public key from base64 or PEM format.
// Supports both base64-encoded X.509 keys and PEM-wrapped keys.
func parsePublicKey(keyStr string) (*rsa.PublicKey, error) {
	var keyBytes []byte
	var err error

	// Decode base64 if not PEM format
	if strings.Contains(keyStr, "-----BEGIN") {
		keyBytes = []byte(keyStr)
	} else {
		keyBytes, err = base64.StdEncoding.DecodeString(keyStr)
		if err != nil {
			return nil, err
		}
	}

	// Extract key data from PEM block if present
	if strings.Contains(keyStr, "-----BEGIN") {
		block, _ := pem.Decode(keyBytes)
		if block == nil {
			return nil, errors.New("failed to parse PEM block")
		}
		keyBytes = block.Bytes
	}

	// Parse X.509 public key
	parsedKey, err := x509.ParsePKIXPublicKey(keyBytes)
	if err != nil {
		return nil, err
	}

	publicKey, ok := parsedKey.(*rsa.PublicKey)
	if !ok {
		return nil, errors.New("not an RSA public key")
	}

	return publicKey, nil
}

// Sign generates MD5 signature for the given openId.
// Signature = MD5("a488109389ef32d6ad546296b3260562" + openId)
func Sign(openId string) string {
	data := "a488109389ef32d6ad546296b3260562" + openId
	hash := md5.Sum([]byte(data))
	return hex.EncodeToString(hash[:])
}
