package nip26

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/btcsuite/btcd/btcec/v2/schnorr"
	"github.com/nbd-wtf/go-nostr"
)

type DelegationToken struct {
	delegator [32]byte
	token     [64]byte
	kinds     []int
	since     *time.Time
	until     *time.Time
	tag       [4]string
}

// Tag() returns the nostr formatted delegation tag for the DelegationToken d.
func (d *DelegationToken) Tag() nostr.Tag {
	return nostr.Tag(d.tag[:])
}

// Conditions() is the delegation conditions string as in NIP-26.
func (d *DelegationToken) Conditions() (conditions string) {
	for _, k := range d.kinds {
		conditions += fmt.Sprintf("kind=%d&", k)
	}
	if d.since != nil {
		conditions += fmt.Sprintf("created_at>%d&", d.since.Unix())
	}
	if d.until != nil {
		conditions += fmt.Sprintf("created_at<%d&", d.until.Unix())
	}
	return strings.TrimSuffix(conditions, "&")
}

// This error is returned by d.Parse(ev) if the event ev does not have a delegation token.
var NoDelegationTag error = fmt.Errorf("No Delegation Tag")

// This error is returned by Import(t,delegatee_pk) if the token signature verification fails.
var VerificationFailed error = fmt.Errorf("VerificationFailed")

// CheckDelegation reads the event and reports whether or not it is correctly delegated.
// If there is a delegation tag, the delegation token signature will be checked according to NIP-26.
// If there is no delegation tag, ok will be true and err will be nil.
// For checking many events, it is advisable to use Parse to reduce the number of memory allocations.
func CheckDelegation(ev *nostr.Event) (ok bool, err error) {
	d := new(DelegationToken)
	if ok, err := d.Parse(ev); ok == true && (err == nil || err == NoDelegationTag) {
		return true, nil
	} else {
		return false, err
	}
}

// Import verifies that t is NIP-26 delegation token for the given delegatee.
// The returned DelegationToken object can be used in DelegatedSign.
// If the token signature verification fails, the error VerificationFailed will be returned.
func Import(t nostr.Tag, delegatee_pk string) (d *DelegationToken, e error) {
	d = new(DelegationToken)
	if len(t) == 4 && t[0] == "delegation" {
		copy(d.tag[:], t)
	} else {
		return nil, fmt.Errorf("not a delegation tag")
	}
	if n, e := hex.Decode(d.delegator[:], []byte(d.tag[1])); n != 32 || e != nil {
		return nil, fmt.Errorf("invalid delegation tag")
	}
	if n, e := hex.Decode(d.token[:], []byte(d.tag[3])); n != 64 || e != nil {
		return nil, fmt.Errorf("invalid delegation tag")
	}
	if d.kinds, d.since, d.until, e = parseConditions(d.tag[2]); e != nil {
		return nil, fmt.Errorf("invalid conditions string")
	}

	// compute the digest
	h := sha256.Sum256([]byte(fmt.Sprintf("nostr:delegation:%s:%s", delegatee_pk, d.tag[2])))

	sig, err := schnorr.ParseSignature(d.token[:])
	if err != nil {
		return nil, fmt.Errorf("Error: %s", err.Error())
	}

	pubkey, err := schnorr.ParsePubKey(d.delegator[:])
	if err != nil {
		return nil, fmt.Errorf("Error: %s", err.Error())
	}
	if !sig.Verify(h[:], pubkey) {
		return nil, VerificationFailed
	}
	return d, nil
}

// Parse reads the event ev and stores the delegation token into d.
// The ok value verifies the event is correctly delegated.
// If there is no delegation token, then d will not be changed. In this case the error value will be `NoDelegationTag`, and ok will be set to true.
// Parse does NOT verify the event was correctly signed. Use ev.CheckSignature() for this.
// More efficient memory allocations versus CheckDelegation(ev) if many events need to be checked.
func (d *DelegationToken) Parse(ev *nostr.Event) (ok bool, err error) {
	for _, t := range ev.Tags {
		if t[0] == "delegation" && len(t) == 4 {
			copy(d.tag[:], t)
			goto jump1
		}
	}
	// event has no delegation. set the token to nil and return.
	return true, NoDelegationTag

jump1:
	if n, e := hex.Decode(d.delegator[:], []byte(d.tag[1])); n != 32 || e != nil {
		return false, fmt.Errorf("invalid delegation tag")
	}
	if n, e := hex.Decode(d.token[:], []byte(d.tag[3])); n != 64 || e != nil {
		return false, fmt.Errorf("invalid delegation tag")
	}

	if d.kinds, d.since, d.until, err = parseConditions(d.tag[2]); err != nil {
		return false, fmt.Errorf("invalid conditions string")
	}

	if len(d.kinds) > 0 {
		for _, k := range d.kinds {
			if ev.Kind == k {
				goto jump2
			}
		}
		return false, fmt.Errorf("Event kind %d is not allowed in delegation conditions.", ev.Kind)
	}

jump2:
	if d.since != nil && ev.CreatedAt.Time().Before(*d.since) {
		return false, fmt.Errorf("Event is created before delegation conditions allow.")
	}
	if d.until != nil && ev.CreatedAt.Time().After(*d.until) {
		return false, fmt.Errorf("Event is created after delegation conditions allow.")
	}

	// compute the digest
	h := sha256.Sum256([]byte(fmt.Sprintf("nostr:delegation:%s:%s", ev.PubKey, d.tag[2])))

	sig, err := schnorr.ParseSignature(d.token[:])
	if err != nil {
		return false, fmt.Errorf("Error: %s", err.Error())
	}

	pubkey, err := schnorr.ParsePubKey(d.delegator[:])
	if err != nil {
		return false, fmt.Errorf("Error: %s", err.Error())
	}

	return sig.Verify(h[:], pubkey), nil
}

// DelegatedSign performs a delegated signature on the event ev.
// The delegation signature is not verified. If desired, the caller can ensure the delegation signature is correct by calling d.Parse(ev) or CheckDelegation(ev) afterwards.
func DelegatedSign(ev *nostr.Event, d *DelegationToken, delegatee_sk string) error {
	for _, t := range ev.Tags {
		if t[0] == "delegation" {
			return fmt.Errorf("event already has delegation token")
		}
	}
	if d.since != nil && ev.CreatedAt.Time().Before(*d.since) || d.until != nil && ev.CreatedAt.Time().After(*d.until) {
		return fmt.Errorf("event created_at field is not compatible with delegation token time conditions.")
	}
	if len(d.kinds) > 0 {
		for _, k := range d.kinds {
			if ev.Kind == k {
				goto jump
			}
		}
		return fmt.Errorf("event kind %d is not compatible with delegation token conditions.", ev.Kind)
	}
jump:
	if pk, e := nostr.GetPublicKey(delegatee_sk); e != nil {
		return fmt.Errorf("invalid delegatee secret key.")
	} else {
		ev.PubKey = pk
	}
	ev.Tags = append(ev.Tags, d.Tag())
	return ev.Sign(delegatee_sk)
}

// CreateToken creates a DelegationToken according to NIP-26.
func CreateToken(delegator_sk string, delegatee_pk string, kinds []int, since *time.Time, until *time.Time) (d *DelegationToken, e error) {
	d = new(DelegationToken)
	s, e := hex.DecodeString(delegator_sk)
	if e != nil {
		return nil, fmt.Errorf("invalid delegator secret key")
	}

	tee_pk, e := hex.DecodeString(delegatee_pk)
	if len(tee_pk) != 32 || e != nil {
		return nil, fmt.Errorf("invalid delegatee pubkey")
	}

	// set delegator
	sk, tor_pk := btcec.PrivKeyFromBytes(s)
	copy(d.delegator[:], schnorr.SerializePubKey(tor_pk))

	d.kinds = kinds
	d.since = since
	d.until = until

	// generate tag
	d.tag[0] = "delegation"
	d.tag[1] = fmt.Sprintf("%x", d.delegator)
	d.tag[2] = d.Conditions()

	h := sha256.Sum256([]byte(fmt.Sprintf("nostr:delegation:%x:%s", tee_pk, d.tag[2])))

	if sig, err := schnorr.Sign(sk, h[:]); err != nil {
		panic(err)
	} else {
		copy(d.token[:], sig.Serialize())
	}

	d.tag[3] = fmt.Sprintf("%x", d.token)

	return d, nil
}

func parseConditions(conditions string) (kinds []int, since *time.Time, until *time.Time, err error) {
	kinds = make([]int, 0)
	for _, v := range strings.Split(conditions, "&") {
		switch {
		case strings.HasPrefix(v, "kind="):
			if i, e := strconv.ParseInt(strings.TrimPrefix(v, "kind="), 10, 64); e == nil {
				kinds = append(kinds, int(i))
			} else {
				return nil, nil, nil, fmt.Errorf("Invalid: %s!", v)
			}
		case strings.HasPrefix(v, "created_at>") && since == nil:
			if i, e := strconv.ParseInt(strings.TrimPrefix(v, "created_at>"), 10, 64); e == nil {
				t := time.Unix(i, 0)
				since = &t
			} else {
				return nil, nil, nil, fmt.Errorf("Invalid: %s", v)
			}
		case strings.HasPrefix(v, "created_at<") && until == nil:
			if i, e := strconv.ParseInt(strings.TrimPrefix(v, "created_at<"), 10, 64); e == nil {
				t := time.Unix(i, 0)
				until = &t
			} else {
				return nil, nil, nil, fmt.Errorf("Invalid: %s", v)
			}
		default:
			return nil, nil, nil, fmt.Errorf("Invalid: %s", v)
		}
	}
	return
}