package planmeta

import (
	"encoding/base64"
	"encoding/hex"
	"fmt"
	"strings"

	"github.com/golang/snappy"
	"github.com/pingcap/tidb/pkg/util/plancodec"
)

// ResolvePlanText picks plan text from topsql_plan_meta columns.
// Prefer plaintext normalized_plan; otherwise decode encoded_normalized_plan.
func ResolvePlanText(normalizedPlan, encodedNormalizedPlan string) (string, error) {
	if strings.TrimSpace(normalizedPlan) != "" {
		return normalizedPlan, nil
	}
	if strings.TrimSpace(encodedNormalizedPlan) == "" {
		return "", nil
	}
	return DecodeEncodedNormalizedPlan(encodedNormalizedPlan)
}

// DecodeEncodedNormalizedPlan decodes topsql_plan_meta.encoded_normalized_plan.
//
// Spec (TiDB TopSQL meta):
//  1. base64 decode
//  2. snappy decompress
//  3. plancodec.DecodeNormalizedPlan
//
// Some lake writers store the payload as a hex string of the base64/snappy bytes;
// those forms are tried as fallbacks when the primary path fails.
func DecodeEncodedNormalizedPlan(encoded string) (string, error) {
	encoded = strings.TrimSpace(encoded)
	if encoded == "" {
		return "", nil
	}

	var primaryErr error
	if text, err := decodeBase64SnappyPlan(encoded); err == nil && text != "" {
		return text, nil
	} else if err != nil {
		primaryErr = err
	}

	if !looksLikeHex(encoded) {
		if primaryErr != nil {
			return "", primaryErr
		}
		return "", fmt.Errorf("decode encoded_normalized_plan: empty plan text")
	}

	raw, err := hex.DecodeString(encoded)
	if err != nil {
		return "", fmt.Errorf("hex decode encoded_normalized_plan: %w", err)
	}
	if text, err := decodeSnappyPlan(raw); err == nil && text != "" {
		return text, nil
	}
	if text, err := decodeBase64SnappyPlan(string(raw)); err == nil && text != "" {
		return text, nil
	}
	if primaryErr != nil {
		return "", primaryErr
	}
	return "", fmt.Errorf("decode encoded_normalized_plan: unsupported payload")
}

func decodeBase64SnappyPlan(encoded string) (string, error) {
	compressed, err := base64.StdEncoding.DecodeString(encoded)
	if err != nil {
		return "", fmt.Errorf("base64 decode encoded_normalized_plan: %w", err)
	}
	return decodeSnappyPlan(compressed)
}

func decodeSnappyPlan(compressed []byte) (string, error) {
	binaryPlan, err := snappy.Decode(nil, compressed)
	if err != nil {
		return "", fmt.Errorf("snappy decode encoded_normalized_plan: %w", err)
	}
	text, err := plancodec.DecodeNormalizedPlan(string(binaryPlan))
	if err != nil {
		return "", fmt.Errorf("plancodec decode encoded_normalized_plan: %w", err)
	}
	return text, nil
}

func looksLikeHex(s string) bool {
	if len(s) < 2 || len(s)%2 != 0 {
		return false
	}
	for i := 0; i < len(s); i++ {
		c := s[i]
		switch {
		case c >= '0' && c <= '9':
		case c >= 'a' && c <= 'f':
		case c >= 'A' && c <= 'F':
		default:
			return false
		}
	}
	return true
}
