package services

import (
	helper "api-service/internal/helpers/bpjs"
	"bytes"
	"compress/gzip"
	"crypto/aes"
	"crypto/cipher"
	"crypto/sha256"
	"encoding/base64"
	"errors"
	"io"
	"log"
	"unicode/utf16"

	lzstring "github.com/daku10/go-lz-string"
)

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

// ResponseVclaim decrypts the encrypted response from VClaim API
func ResponseVclaim(encrypted string, key string) (string, error) {
	log.Println("ResponseVclaim: Starting decryption process")
	log.Printf("ResponseVclaim: Encrypted string length: %d", len(encrypted))

	// Pad the base64 string if needed
	if len(encrypted)%4 != 0 {
		padding := (4 - len(encrypted)%4) % 4
		for i := 0; i < padding; i++ {
			encrypted += "="
		}
	}

	// Decode base64
	cipherText, err := base64.StdEncoding.DecodeString(encrypted)
	if err != nil {
		log.Printf("ResponseVclaim: Failed to decode base64: %v", err)
		return "", err
	}

	if len(cipherText) < aes.BlockSize {
		return "", errors.New("cipherText too short")
	}

	// Create AES cipher
	hash := sha256.Sum256([]byte(key))
	block, err := aes.NewCipher(hash[:])
	if err != nil {
		return "", err
	}

	// Try both IV methods
	// Method 1: IV from hash (current approach)
	if result, err := tryDecryptWithHashIV(cipherText, block, hash[:aes.BlockSize]); err == nil {
		log.Printf("ResponseVclaim: Success with hash IV method")
		return result, nil
	}

	// Method 2: IV from cipherText (standard approach)
	if result, err := tryDecryptWithCipherIV(cipherText, block); err == nil {
		log.Printf("ResponseVclaim: Success with cipher IV method")
		return result, nil
	}

	return "", errors.New("all decryption methods failed")
}

func tryDecryptWithHashIV(cipherText []byte, block cipher.Block, iv []byte) (string, error) {
	if len(cipherText)%aes.BlockSize != 0 {
		return "", errors.New("cipherText is not a multiple of the block size")
	}

	mode := cipher.NewCBCDecrypter(block, iv)
	decrypted := make([]byte, len(cipherText))
	mode.CryptBlocks(decrypted, cipherText)

	// Remove PKCS7 padding
	decrypted = helper.RemovePKCS7Padding(decrypted)
	log.Printf("tryDecryptWithHashIV: Decryption completed, length: %d", len(decrypted))

	return tryAllDecompressionMethods(decrypted)
}

func tryDecryptWithCipherIV(cipherText []byte, block cipher.Block) (string, error) {
	if len(cipherText) < aes.BlockSize {
		return "", errors.New("cipherText too short for IV extraction")
	}

	// Extract IV from first block
	iv := cipherText[:aes.BlockSize]
	cipherData := cipherText[aes.BlockSize:]

	if len(cipherData)%aes.BlockSize != 0 {
		return "", errors.New("cipher data is not a multiple of the block size")
	}

	mode := cipher.NewCBCDecrypter(block, iv)
	decrypted := make([]byte, len(cipherData))
	mode.CryptBlocks(decrypted, cipherData)

	// Remove PKCS7 padding
	decrypted = helper.RemovePKCS7Padding(decrypted)
	log.Printf("tryDecryptWithCipherIV: Decryption completed, length: %d", len(decrypted))

	return tryAllDecompressionMethods(decrypted)
}

func tryAllDecompressionMethods(data []byte) (string, error) {
	log.Printf("tryAllDecompressionMethods: Attempting decompression, data length: %d", len(data))

	// Method 1: Check if it's already valid JSON
	if isValidJSON(data) {
		log.Println("tryAllDecompressionMethods: Data is valid JSON, returning as-is")
		return string(data), nil
	}

	// Method 2: Try gzip decompression
	if result, err := tryGzipDecompression(data); err == nil && len(result) > 0 {
		log.Println("tryAllDecompressionMethods: Gzip decompression successful")
		return result, nil
	}

	// Method 3: Try LZ-string decompression methods
	if result, err := tryLZStringMethods(data); err == nil && len(result) > 0 {
		log.Println("tryAllDecompressionMethods: LZ-string decompression successful")
		return result, nil
	}

	// Method 4: Return as plain text
	result := string(data)
	if len(result) > 0 {
		log.Printf("tryAllDecompressionMethods: Using decrypted data as plain text, length: %d", len(result))
		return result, nil
	}

	return "", errors.New("all decompression methods failed")
}

func isValidJSON(data []byte) bool {
	if len(data) == 0 {
		return false
	}
	firstChar := data[0]
	return firstChar == '{' || firstChar == '['
}

func tryGzipDecompression(data []byte) (string, error) {
	reader, err := gzip.NewReader(bytes.NewReader(data))
	if err != nil {
		return "", err
	}
	defer reader.Close()

	decompressed, err := io.ReadAll(reader)
	if err != nil {
		return "", err
	}

	return string(decompressed), nil
}

func tryLZStringMethods(data []byte) (string, error) {
	dataStr := string(data)

	// Method 1: DecompressFromEncodedURIComponent
	if result, err := lzstring.DecompressFromEncodedURIComponent(dataStr); err == nil && len(result) > 0 {
		return result, nil
	}

	// Method 2: DecompressFromBase64
	if result, err := lzstring.DecompressFromBase64(dataStr); err == nil && len(result) > 0 {
		return result, nil
	}

	// Method 3: DecompressFromUTF16 (with proper conversion)
	if utf16Data, err := stringToUTF16(dataStr); err == nil {
		if result, err := lzstring.DecompressFromUTF16(utf16Data); err == nil && len(result) > 0 {
			return result, nil
		}
	}

	// Method 4: Try with base64 decoding first
	if decoded, err := base64.StdEncoding.DecodeString(dataStr); err == nil {
		if result, err := lzstring.DecompressFromEncodedURIComponent(string(decoded)); err == nil && len(result) > 0 {
			return result, nil
		}
	}

	return "", errors.New("all LZ-string methods failed")
}

// stringToUTF16 converts string to []uint16 for UTF16 decompression
func stringToUTF16(s string) ([]uint16, error) {
	if len(s) == 0 {
		return nil, errors.New("empty string")
	}

	// Convert string to runes first
	runes := []rune(s)

	// Convert runes to UTF16
	utf16Data := utf16.Encode(runes)

	return utf16Data, nil
}
