package catalogapp

import (
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"github.com/go-sql-driver/mysql"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
)

func digestParts(parts ...string) string {
	hash := sha256.New()
	for _, part := range parts {
		_, _ = hash.Write([]byte{0})
		_, _ = hash.Write([]byte(part))
	}
	return "sha256:" + hex.EncodeToString(hash.Sum(nil))
}

func hashKey(parts ...string) string {
	return strings.TrimPrefix(digestParts(parts...), "sha256:")
}

func epochText(value uint64) string { return strconv.FormatUint(value, 10) }

func parseEpoch(raw []byte) (uint64, error) {
	value, err := strconv.ParseUint(string(raw), 10, 64)
	if err != nil {
		return 0, fmt.Errorf("invalid Catalog epoch: %w", err)
	}
	return value, nil
}

func canonicalStrings(label string, values []string, allowEmpty bool) ([]string, error) {
	result := append([]string(nil), values...)
	if !sort.StringsAreSorted(result) {
		return nil, fmt.Errorf("%s must be bytewise sorted", label)
	}
	for i, value := range result {
		if strings.TrimSpace(value) == "" || (i > 0 && value == result[i-1]) {
			return nil, fmt.Errorf("%s must be non-empty and duplicate-free", label)
		}
	}
	if !allowEmpty && len(result) == 0 {
		return nil, fmt.Errorf("%s must be non-empty", label)
	}
	return result, nil
}

func marshalJSON(value any) ([]byte, error) {
	encoded, err := json.Marshal(value)
	if err != nil {
		return nil, err
	}
	return encoded, nil
}

func isDuplicate(err error) bool {
	var mysqlError *mysql.MySQLError
	return errors.As(err, &mysqlError) && mysqlError.Number == 1062
}

func isMissingTable(err error) bool {
	var mysqlError *mysql.MySQLError
	return errors.As(err, &mysqlError) && mysqlError.Number == 1146
}

func nullableString(value string) any {
	if value == "" {
		return nil
	}
	return value
}

func validateOrgAndOpaqueID(orgID, label, value string) error {
	if strings.TrimSpace(orgID) == "" || len(orgID) > 255 {
		return fmt.Errorf("OrgID is required and must fit 255 bytes")
	}
	if strings.TrimSpace(value) == "" {
		return fmt.Errorf("%s is required", label)
	}
	return nil
}

func requireDigest(label, value string) error {
	if err := appauthority.ValidateDigest(value); err != nil {
		return fmt.Errorf("%s: %w", label, err)
	}
	return nil
}

func rollback(tx *sql.Tx) { _ = tx.Rollback() }
