// Package secp provides Go bindings to libsecp256k1 without CGO.
// It uses dynamic library loading via purego to call C functions directly.
package secp

import (
	"fmt"
	"log"
	"runtime"
	"sync"
	"unsafe"

	"github.com/ebitengine/purego"
)

// Constants for context flags
const (
	ContextNone       = 1
	ContextVerify     = 257  // 1 | (1 << 8)
	ContextSign       = 513  // 1 | (1 << 9)
	ContextDeclassify = 1025 // 1 | (1 << 10)
)

// EC flags
const (
	ECCompressed   = 258 // SECP256K1_EC_COMPRESSED
	ECUncompressed = 2   // SECP256K1_EC_UNCOMPRESSED
)

// Size constants
const (
	PublicKeySize             = 64
	CompressedPublicKeySize   = 33
	UncompressedPublicKeySize = 65
	SignatureSize             = 64
	CompactSignatureSize      = 64
	PrivateKeySize            = 32
	SharedSecretSize          = 32
	SchnorrSignatureSize      = 64
	RecoverableSignatureSize  = 65
)

var (
	libHandle   uintptr
	loadLibOnce sync.Once
	loadLibErr  error
)

// Function pointers
var (
	contextCreate                  func(flags uint32) uintptr
	contextDestroy                 func(ctx uintptr)
	contextRandomize               func(ctx uintptr, seed32 *byte) int32
	ecPubkeyCreate                 func(ctx uintptr, pubkey *byte, seckey *byte) int32
	ecPubkeySerialize              func(ctx uintptr, output *byte, outputlen *uint64, pubkey *byte, flags uint32) int32
	ecPubkeyParse                  func(ctx uintptr, pubkey *byte, input *byte, inputlen uint64) int32
	ecdsaSign                      func(ctx uintptr, sig *byte, msg32 *byte, seckey *byte, noncefp uintptr, ndata uintptr) int32
	ecdsaVerify                    func(ctx uintptr, sig *byte, msg32 *byte, pubkey *byte) int32
	ecdsaSignatureSerializeDer     func(ctx uintptr, output *byte, outputlen *uint64, sig *byte) int32
	ecdsaSignatureParseDer         func(ctx uintptr, sig *byte, input *byte, inputlen uint64) int32
	ecdsaSignatureSerializeCompact func(ctx uintptr, output64 *byte, sig *byte) int32
	ecdsaSignatureParseCompact     func(ctx uintptr, sig *byte, input64 *byte) int32
	ecdsaSignatureNormalize        func(ctx uintptr, sigout *byte, sigin *byte) int32

	// Schnorr functions
	schnorrsigSign32     func(ctx uintptr, sig64 *byte, msg32 *byte, keypair *byte, auxrand32 *byte) int32
	schnorrsigVerify     func(ctx uintptr, sig64 *byte, msg32 *byte, msglen uint64, pubkey *byte) int32
	keypairCreate        func(ctx uintptr, keypair *byte, seckey *byte) int32
	xonlyPubkeyParse     func(ctx uintptr, pubkey *byte, input32 *byte) int32
	xonlyPubkeySerialize func(ctx uintptr, output32 *byte, pubkey *byte) int32
	keypairXonlyPub      func(ctx uintptr, pubkey *byte, pkParity *int32, keypair *byte) int32
	keypairPub           func(ctx uintptr, pubkey *byte, keypair *byte) int32

	// ECDH functions
	ecdh func(ctx uintptr, output *byte, pubkey *byte, seckey *byte, hashfp uintptr, data uintptr) int32

	// Recovery functions
	ecdsaRecoverableSignatureSerializeCompact func(ctx uintptr, output64 *byte, recid *int32, sig *byte) int32
	ecdsaRecoverableSignatureParseCompact     func(ctx uintptr, sig *byte, input64 *byte, recid int32) int32
	ecdsaSignRecoverable                      func(ctx uintptr, sig *byte, msg32 *byte, seckey *byte, noncefp uintptr, ndata uintptr) int32
	ecdsaRecover                              func(ctx uintptr, pubkey *byte, sig *byte, msg32 *byte) int32

	// Extrakeys
	xonlyPubkeyFromPubkey func(ctx uintptr, xonlyPubkey *byte, pkParity *int32, pubkey *byte) int32
)

// LoadLibrary loads the libsecp256k1 shared library
func LoadLibrary() (err error) {
	loadLibOnce.Do(func() {
		var libPath string

		// Try to find the library
		switch runtime.GOOS {
		case "linux":
			// Try common library paths
			// For linux/amd64, try the bundled library first
			paths := []string{
				"./libsecp256k1.so", // Bundled in repo for linux amd64
				"libsecp256k1.so.5",
				"libsecp256k1.so.2",
				"libsecp256k1.so.1",
				"libsecp256k1.so.0",
				"libsecp256k1.so",
				"/usr/lib/libsecp256k1.so",
				"/usr/local/lib/libsecp256k1.so",
				"/usr/lib/x86_64-linux-gnu/libsecp256k1.so",
			}
			for _, p := range paths {
				libHandle, err = purego.Dlopen(p, purego.RTLD_NOW|purego.RTLD_GLOBAL)
				if err == nil {
					libPath = p
					break
				}
			}
		case "darwin":
			paths := []string{
				"libsecp256k1.2.dylib",
				"libsecp256k1.1.dylib",
				"libsecp256k1.0.dylib",
				"libsecp256k1.dylib",
				"/usr/local/lib/libsecp256k1.dylib",
				"/opt/homebrew/lib/libsecp256k1.dylib",
			}
			for _, p := range paths {
				libHandle, err = purego.Dlopen(p, purego.RTLD_NOW|purego.RTLD_GLOBAL)
				if err == nil {
					libPath = p
					break
				}
			}
		case "windows":
			paths := []string{
				"libsecp256k1-2.dll",
				"libsecp256k1-1.dll",
				"libsecp256k1-0.dll",
				"libsecp256k1.dll",
				"secp256k1.dll",
			}
			for _, p := range paths {
				libHandle, err = purego.Dlopen(p, purego.RTLD_NOW|purego.RTLD_GLOBAL)
				if err == nil {
					libPath = p
					break
				}
			}
		default:
			err = fmt.Errorf("unsupported platform: %s", runtime.GOOS)
			loadLibErr = err
			return
		}

		if err != nil {
			loadLibErr = fmt.Errorf("failed to load libsecp256k1: %w", err)
			return
		}

		// Register symbols
		if err = registerSymbols(); err != nil {
			loadLibErr = fmt.Errorf("failed to register symbols from %s: %w", libPath, err)
			return
		}

		log.Printf("INFO: Successfully loaded libsecp256k1 v5.0.0 from %s", libPath)
		loadLibErr = nil
	})

	return loadLibErr
}

// registerSymbols registers all C function symbols
func registerSymbols() (err error) {
	// Core context functions
	purego.RegisterLibFunc(&contextCreate, libHandle, "secp256k1_context_create")
	purego.RegisterLibFunc(&contextDestroy, libHandle, "secp256k1_context_destroy")
	purego.RegisterLibFunc(&contextRandomize, libHandle, "secp256k1_context_randomize")

	// Public key functions
	purego.RegisterLibFunc(&ecPubkeyCreate, libHandle, "secp256k1_ec_pubkey_create")
	purego.RegisterLibFunc(&ecPubkeySerialize, libHandle, "secp256k1_ec_pubkey_serialize")
	purego.RegisterLibFunc(&ecPubkeyParse, libHandle, "secp256k1_ec_pubkey_parse")

	// ECDSA functions
	purego.RegisterLibFunc(&ecdsaSign, libHandle, "secp256k1_ecdsa_sign")
	purego.RegisterLibFunc(&ecdsaVerify, libHandle, "secp256k1_ecdsa_verify")
	purego.RegisterLibFunc(&ecdsaSignatureSerializeDer, libHandle, "secp256k1_ecdsa_signature_serialize_der")
	purego.RegisterLibFunc(&ecdsaSignatureParseDer, libHandle, "secp256k1_ecdsa_signature_parse_der")
	purego.RegisterLibFunc(&ecdsaSignatureSerializeCompact, libHandle, "secp256k1_ecdsa_signature_serialize_compact")
	purego.RegisterLibFunc(&ecdsaSignatureParseCompact, libHandle, "secp256k1_ecdsa_signature_parse_compact")
	purego.RegisterLibFunc(&ecdsaSignatureNormalize, libHandle, "secp256k1_ecdsa_signature_normalize")

	// Try to load optional modules - don't fail if they're not available

	// Schnorr module
	tryRegister(&schnorrsigSign32, "secp256k1_schnorrsig_sign32")
	tryRegister(&schnorrsigVerify, "secp256k1_schnorrsig_verify")
	tryRegister(&keypairCreate, "secp256k1_keypair_create")
	tryRegister(&xonlyPubkeyParse, "secp256k1_xonly_pubkey_parse")
	tryRegister(&xonlyPubkeySerialize, "secp256k1_xonly_pubkey_serialize")
	tryRegister(&keypairXonlyPub, "secp256k1_keypair_xonly_pub")
	tryRegister(&keypairPub, "secp256k1_keypair_pub")
	tryRegister(&xonlyPubkeyFromPubkey, "secp256k1_xonly_pubkey_from_pubkey")

	// ECDH module
	tryRegister(&ecdh, "secp256k1_ecdh")

	// Recovery module
	tryRegister(&ecdsaRecoverableSignatureSerializeCompact, "secp256k1_ecdsa_recoverable_signature_serialize_compact")
	tryRegister(&ecdsaRecoverableSignatureParseCompact, "secp256k1_ecdsa_recoverable_signature_parse_compact")
	tryRegister(&ecdsaSignRecoverable, "secp256k1_ecdsa_sign_recoverable")
	tryRegister(&ecdsaRecover, "secp256k1_ecdsa_recover")

	return nil
}

// tryRegister attempts to register a symbol without failing if it doesn't exist
func tryRegister(fptr interface{}, symbol string) {
	defer func() {
		if r := recover(); r != nil {
			// Symbol not found, ignore
		}
	}()
	purego.RegisterLibFunc(fptr, libHandle, symbol)
}

// Context represents a secp256k1 context
type Context struct {
	ctx uintptr
}

// NewContext creates a new secp256k1 context
func NewContext(flags uint32) (c *Context, err error) {
	if err = LoadLibrary(); err != nil {
		return
	}

	ctx := contextCreate(flags)
	if ctx == 0 {
		err = fmt.Errorf("failed to create context")
		return
	}

	c = &Context{ctx: ctx}
	runtime.SetFinalizer(c, (*Context).Destroy)

	return
}

// Destroy destroys the context
func (c *Context) Destroy() {
	if c.ctx != 0 {
		contextDestroy(c.ctx)
		c.ctx = 0
	}
}

// Randomize randomizes the context with entropy
func (c *Context) Randomize(seed32 []byte) (err error) {
	if len(seed32) != 32 {
		err = fmt.Errorf("seed must be 32 bytes")
		return
	}

	ret := contextRandomize(c.ctx, &seed32[0])
	if ret != 1 {
		err = fmt.Errorf("failed to randomize context")
		return
	}

	return
}

// CreatePublicKey creates a public key from a private key
func (c *Context) CreatePublicKey(seckey []byte) (pubkey []byte, err error) {
	if len(seckey) != PrivateKeySize {
		err = fmt.Errorf("private key must be %d bytes", PrivateKeySize)
		return
	}

	pubkey = make([]byte, PublicKeySize)
	ret := ecPubkeyCreate(c.ctx, &pubkey[0], &seckey[0])
	if ret != 1 {
		err = fmt.Errorf("failed to create public key")
		return
	}

	return
}

// SerializePublicKey serializes a public key
func (c *Context) SerializePublicKey(pubkey []byte, compressed bool) (output []byte, err error) {
	if len(pubkey) != PublicKeySize {
		err = fmt.Errorf("public key must be %d bytes", PublicKeySize)
		return
	}

	var flags uint32
	if compressed {
		output = make([]byte, CompressedPublicKeySize)
		flags = ECCompressed
	} else {
		output = make([]byte, UncompressedPublicKeySize)
		flags = ECUncompressed
	}

	outputLen := uint64(len(output))
	ret := ecPubkeySerialize(c.ctx, &output[0], &outputLen, &pubkey[0], flags)
	if ret != 1 {
		err = fmt.Errorf("failed to serialize public key")
		return
	}

	output = output[:outputLen]
	return
}

// SerializePublicKeyCompressed serializes a public key in compressed format (33 bytes)
func (c *Context) SerializePublicKeyCompressed(pubkey []byte) (output []byte, err error) {
	return c.SerializePublicKey(pubkey, true)
}

// ParsePublicKey parses a serialized public key
func (c *Context) ParsePublicKey(input []byte) (pubkey []byte, err error) {
	pubkey = make([]byte, PublicKeySize)
	ret := ecPubkeyParse(c.ctx, &pubkey[0], &input[0], uint64(len(input)))
	if ret != 1 {
		err = fmt.Errorf("failed to parse public key")
		return
	}

	return
}

// Sign creates an ECDSA signature
func (c *Context) Sign(msg32 []byte, seckey []byte) (sig []byte, err error) {
	if len(msg32) != 32 {
		err = fmt.Errorf("message must be 32 bytes")
		return
	}
	if len(seckey) != PrivateKeySize {
		err = fmt.Errorf("private key must be %d bytes", PrivateKeySize)
		return
	}

	sig = make([]byte, SignatureSize)
	ret := ecdsaSign(c.ctx, &sig[0], &msg32[0], &seckey[0], 0, 0)
	if ret != 1 {
		err = fmt.Errorf("failed to sign message")
		return
	}

	return
}

// Verify verifies an ECDSA signature
func (c *Context) Verify(msg32 []byte, sig []byte, pubkey []byte) (valid bool, err error) {
	if len(msg32) != 32 {
		err = fmt.Errorf("message must be 32 bytes")
		return
	}
	if len(sig) != SignatureSize {
		err = fmt.Errorf("signature must be %d bytes", SignatureSize)
		return
	}
	if len(pubkey) != PublicKeySize {
		err = fmt.Errorf("public key must be %d bytes", PublicKeySize)
		return
	}

	ret := ecdsaVerify(c.ctx, &sig[0], &msg32[0], &pubkey[0])
	valid = ret == 1

	return
}

// SerializeSignatureDER serializes a signature in DER format
func (c *Context) SerializeSignatureDER(sig []byte) (output []byte, err error) {
	if len(sig) != SignatureSize {
		err = fmt.Errorf("signature must be %d bytes", SignatureSize)
		return
	}

	output = make([]byte, 72) // Max DER signature size
	outputLen := uint64(len(output))

	ret := ecdsaSignatureSerializeDer(c.ctx, &output[0], &outputLen, &sig[0])
	if ret != 1 {
		err = fmt.Errorf("failed to serialize signature")
		return
	}

	output = output[:outputLen]
	return
}

// ParseSignatureDER parses a DER-encoded signature
func (c *Context) ParseSignatureDER(input []byte) (sig []byte, err error) {
	sig = make([]byte, SignatureSize)
	ret := ecdsaSignatureParseDer(c.ctx, &sig[0], &input[0], uint64(len(input)))
	if ret != 1 {
		err = fmt.Errorf("failed to parse DER signature")
		return
	}

	return
}

// SerializeSignatureCompact serializes a signature in compact format (64 bytes)
func (c *Context) SerializeSignatureCompact(sig []byte) (output []byte, err error) {
	if len(sig) != SignatureSize {
		err = fmt.Errorf("signature must be %d bytes", SignatureSize)
		return
	}

	output = make([]byte, CompactSignatureSize)
	ret := ecdsaSignatureSerializeCompact(c.ctx, &output[0], &sig[0])
	if ret != 1 {
		err = fmt.Errorf("failed to serialize signature")
		return
	}

	return
}

// ParseSignatureCompact parses a compact (64-byte) signature
func (c *Context) ParseSignatureCompact(input64 []byte) (sig []byte, err error) {
	if len(input64) != CompactSignatureSize {
		err = fmt.Errorf("compact signature must be %d bytes", CompactSignatureSize)
		return
	}

	sig = make([]byte, SignatureSize)
	ret := ecdsaSignatureParseCompact(c.ctx, &sig[0], &input64[0])
	if ret != 1 {
		err = fmt.Errorf("failed to parse compact signature")
		return
	}

	return
}

// NormalizeSignature normalizes a signature to lower-S form
func (c *Context) NormalizeSignature(sig []byte) (normalized []byte, wasNormalized bool, err error) {
	if len(sig) != SignatureSize {
		err = fmt.Errorf("signature must be %d bytes", SignatureSize)
		return
	}

	normalized = make([]byte, SignatureSize)
	ret := ecdsaSignatureNormalize(c.ctx, &normalized[0], &sig[0])
	wasNormalized = ret == 1

	return
}

// Utility function to convert *byte to unsafe.Pointer
func bytesToPtr(b []byte) unsafe.Pointer {
	if len(b) == 0 {
		return nil
	}
	return unsafe.Pointer(&b[0])
}
