package p256k1

import (
	"errors"
	"sync"

	"github.com/ebitengine/purego"
)

// LibSecp256k1 wraps the native libsecp256k1.so library using purego for CGO-free operation.
// This provides a way to benchmark against the C implementation without CGO.
type LibSecp256k1 struct {
	lib    uintptr
	ctx    uintptr
	loaded bool
	mu     sync.RWMutex

	// Function pointers
	contextCreate      func(uint) uintptr
	contextDestroy     func(uintptr)
	contextRandomize   func(uintptr, *byte) int
	schnorrsigSign32   func(uintptr, *byte, *byte, *byte, *byte) int
	schnorrsigVerify   func(uintptr, *byte, *byte, uint, *byte) int
	keypairCreate      func(uintptr, *byte, *byte) int
	keypairXonlyPub    func(uintptr, *byte, *int, *byte) int
	xonlyPubkeyParse   func(uintptr, *byte, *byte) int
	ecPubkeyCreate       func(uintptr, *byte, *byte) int
	ecPubkeyParse        func(uintptr, *byte, *byte, uint) int
	ecPubkeySerialize    func(uintptr, *byte, *uint, *byte, uint) int
	xonlyPubkeySerialize func(uintptr, *byte, *byte) int
	ecdh                 func(uintptr, *byte, *byte, *byte, uintptr, uintptr) int
}

// Secp256k1 context flags
// In modern libsecp256k1, SECP256K1_CONTEXT_NONE = 1 is the only valid flag.
// The old SIGN (256) and VERIFY (257) flags are deprecated.
const (
	libContextNone = 1
)

// Global instance
var (
	libSecp        *LibSecp256k1
	libSecpOnce    sync.Once
	libSecpInitErr error
)

// GetLibSecp256k1 returns the global LibSecp256k1 instance, loading it if necessary.
// Returns nil and an error if the library cannot be loaded.
func GetLibSecp256k1() (*LibSecp256k1, error) {
	libSecpOnce.Do(func() {
		libSecp = &LibSecp256k1{}
		// Try multiple paths to find the library
		paths := []string{
			"./libsecp256k1.so",
			"../libsecp256k1.so",
			"/home/mleku/src/p256k1.mleku.dev/libsecp256k1.so",
			"libsecp256k1.so",
		}
		for _, path := range paths {
			err := libSecp.Load(path)
			if err == nil {
				libSecpInitErr = nil
				return
			}
			libSecpInitErr = err
		}
	})
	if libSecpInitErr != nil {
		return nil, libSecpInitErr
	}
	return libSecp, nil
}

// Load loads the libsecp256k1.so library from the given path.
func (l *LibSecp256k1) Load(path string) error {
	l.mu.Lock()
	defer l.mu.Unlock()

	if l.loaded {
		return nil
	}

	lib, err := purego.Dlopen(path, purego.RTLD_NOW|purego.RTLD_GLOBAL)
	if err != nil {
		return err
	}
	l.lib = lib

	// Register function pointers
	purego.RegisterLibFunc(&l.contextCreate, lib, "secp256k1_context_create")
	purego.RegisterLibFunc(&l.contextDestroy, lib, "secp256k1_context_destroy")
	purego.RegisterLibFunc(&l.contextRandomize, lib, "secp256k1_context_randomize")
	purego.RegisterLibFunc(&l.schnorrsigSign32, lib, "secp256k1_schnorrsig_sign32")
	purego.RegisterLibFunc(&l.schnorrsigVerify, lib, "secp256k1_schnorrsig_verify")
	purego.RegisterLibFunc(&l.keypairCreate, lib, "secp256k1_keypair_create")
	purego.RegisterLibFunc(&l.keypairXonlyPub, lib, "secp256k1_keypair_xonly_pub")
	purego.RegisterLibFunc(&l.xonlyPubkeyParse, lib, "secp256k1_xonly_pubkey_parse")
	purego.RegisterLibFunc(&l.ecPubkeyCreate, lib, "secp256k1_ec_pubkey_create")
	purego.RegisterLibFunc(&l.ecPubkeyParse, lib, "secp256k1_ec_pubkey_parse")
	purego.RegisterLibFunc(&l.ecPubkeySerialize, lib, "secp256k1_ec_pubkey_serialize")
	purego.RegisterLibFunc(&l.xonlyPubkeySerialize, lib, "secp256k1_xonly_pubkey_serialize")
	purego.RegisterLibFunc(&l.ecdh, lib, "secp256k1_ecdh")

	// Create context (modern libsecp256k1 uses SECP256K1_CONTEXT_NONE = 1)
	l.ctx = l.contextCreate(libContextNone)
	if l.ctx == 0 {
		return errors.New("failed to create secp256k1 context")
	}

	// Randomize context for better security
	var seed [32]byte
	// Use zero seed for deterministic benchmarks
	l.contextRandomize(l.ctx, &seed[0])

	l.loaded = true
	return nil
}

// Close releases the library resources.
func (l *LibSecp256k1) Close() {
	l.mu.Lock()
	defer l.mu.Unlock()

	if !l.loaded {
		return
	}

	if l.ctx != 0 {
		l.contextDestroy(l.ctx)
		l.ctx = 0
	}

	if l.lib != 0 {
		purego.Dlclose(l.lib)
		l.lib = 0
	}

	l.loaded = false
}

// IsLoaded returns true if the library is loaded.
func (l *LibSecp256k1) IsLoaded() bool {
	l.mu.RLock()
	defer l.mu.RUnlock()
	return l.loaded
}

// SchnorrSign signs a 32-byte message using a 32-byte secret key.
// Returns a 64-byte signature.
func (l *LibSecp256k1) SchnorrSign(msg32, seckey32 []byte) ([]byte, error) {
	l.mu.RLock()
	defer l.mu.RUnlock()

	if !l.loaded {
		return nil, errors.New("library not loaded")
	}
	if len(msg32) != 32 {
		return nil, errors.New("message must be 32 bytes")
	}
	if len(seckey32) != 32 {
		return nil, errors.New("secret key must be 32 bytes")
	}

	// Create keypair from secret key
	keypair := make([]byte, 96) // secp256k1_keypair is 96 bytes
	if l.keypairCreate(l.ctx, &keypair[0], &seckey32[0]) != 1 {
		return nil, errors.New("failed to create keypair")
	}

	// Sign
	sig := make([]byte, 64)
	if l.schnorrsigSign32(l.ctx, &sig[0], &msg32[0], &keypair[0], nil) != 1 {
		return nil, errors.New("signing failed")
	}

	return sig, nil
}

// SchnorrVerify verifies a Schnorr signature.
func (l *LibSecp256k1) SchnorrVerify(sig64, msg32, pubkey32 []byte) bool {
	l.mu.RLock()
	defer l.mu.RUnlock()

	if !l.loaded {
		return false
	}
	if len(sig64) != 64 || len(msg32) != 32 || len(pubkey32) != 32 {
		return false
	}

	// Parse x-only pubkey using secp256k1_xonly_pubkey_parse
	xonlyPubkey := make([]byte, 64) // secp256k1_xonly_pubkey is 64 bytes
	if l.xonlyPubkeyParse(l.ctx, &xonlyPubkey[0], &pubkey32[0]) != 1 {
		return false
	}

	result := l.schnorrsigVerify(l.ctx, &sig64[0], &msg32[0], 32, &xonlyPubkey[0])
	return result == 1
}

// CreatePubkey derives a public key from a secret key.
// Returns the 32-byte x-only public key.
func (l *LibSecp256k1) CreatePubkey(seckey32 []byte) ([]byte, error) {
	l.mu.RLock()
	defer l.mu.RUnlock()

	if !l.loaded {
		return nil, errors.New("library not loaded")
	}
	if len(seckey32) != 32 {
		return nil, errors.New("secret key must be 32 bytes")
	}

	// Create keypair
	keypair := make([]byte, 96)
	if l.keypairCreate(l.ctx, &keypair[0], &seckey32[0]) != 1 {
		return nil, errors.New("failed to create keypair")
	}

	// Extract x-only pubkey (internal representation is 64 bytes)
	xonlyPubkey := make([]byte, 64)
	var parity int
	if l.keypairXonlyPub(l.ctx, &xonlyPubkey[0], &parity, &keypair[0]) != 1 {
		return nil, errors.New("failed to extract x-only pubkey")
	}

	// Serialize to get the 32-byte x-coordinate
	pubkey32 := make([]byte, 32)
	if l.xonlyPubkeySerialize(l.ctx, &pubkey32[0], &xonlyPubkey[0]) != 1 {
		return nil, errors.New("failed to serialize x-only pubkey")
	}

	return pubkey32, nil
}

// ECDH computes the shared secret using ECDH.
func (l *LibSecp256k1) ECDH(seckey32, pubkey33 []byte) ([]byte, error) {
	l.mu.RLock()
	defer l.mu.RUnlock()

	if !l.loaded {
		return nil, errors.New("library not loaded")
	}
	if len(seckey32) != 32 {
		return nil, errors.New("secret key must be 32 bytes")
	}
	if len(pubkey33) != 33 && len(pubkey33) != 65 {
		return nil, errors.New("public key must be 33 or 65 bytes")
	}

	// Parse pubkey
	pubkey := make([]byte, 64) // secp256k1_pubkey is 64 bytes
	if l.ecPubkeyParse(l.ctx, &pubkey[0], &pubkey33[0], uint(len(pubkey33))) != 1 {
		return nil, errors.New("failed to parse public key")
	}

	// Compute ECDH
	output := make([]byte, 32)
	if l.ecdh(l.ctx, &output[0], &pubkey[0], &seckey32[0], 0, 0) != 1 {
		return nil, errors.New("ECDH failed")
	}

	return output, nil
}
