package nip60

import (
	"context"
	"fmt"
	"slices"

	"github.com/btcsuite/btcd/btcec/v2"
	"github.com/elnosh/gonuts/cashu"
	"github.com/elnosh/gonuts/cashu/nuts/nut02"
	"github.com/elnosh/gonuts/cashu/nuts/nut03"
	"github.com/elnosh/gonuts/cashu/nuts/nut10"
	"github.com/nbd-wtf/go-nostr/nip60/client"
)

type swapSettings struct {
	spendingCondition *nut10.SpendingCondition
	mustSignOutputs   bool
}

func (w *Wallet) swapProofs(
	ctx context.Context,
	mint string,
	proofs cashu.Proofs,
	targetAmount uint64,
	ss swapSettings,
) (principal cashu.Proofs, change cashu.Proofs, err error) {
	keysets, err := client.GetAllKeysets(ctx, mint)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to get all keysets for %s: %w", mint, err)
	}
	activeKeyset, err := client.GetActiveKeyset(ctx, mint)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to get active keyset for %s: %w", mint, err)
	}
	ksKeys, err := ParseKeysetKeys(activeKeyset.Keys)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to parse keys for %s: %w", mint, err)
	}

	prePrincipal, preChange, err := splitIntoPrincipalAndChange(
		keysets,
		proofs,
		targetAmount,
		activeKeyset.Id,
		ss.spendingCondition,
	)
	if err != nil {
		return nil, nil, err
	}

	if ss.mustSignOutputs {
		for i, output := range prePrincipal.bm {
			prePrincipal.bm[i].Witness, err = signOutput(w.PrivateKey, output)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to sign output message %d: %w", i, err)
			}
		}
	}

	req := nut03.PostSwapRequest{
		Inputs:  proofs,
		Outputs: slices.Concat(prePrincipal.bm, preChange.bm),
	}

	res, err := client.PostSwap(ctx, mint, req)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to swap tokens at %s: %w", mint, err)
	}

	// build the proofs locally from mint's response
	principal, err = constructProofs(prePrincipal, res.Signatures[0:len(prePrincipal.bm)], ksKeys)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to construct principal proofs: %w", err)
	}

	change, err = constructProofs(preChange, res.Signatures[len(prePrincipal.bm):], ksKeys)
	if err != nil {
		return nil, nil, fmt.Errorf("failed to construct principal proofs: %w", err)
	}

	return principal, change, nil
}

type preparedOutputs struct {
	bm      cashu.BlindedMessages
	rs      []*btcec.PrivateKey
	secrets []string
}

func splitIntoPrincipalAndChange(
	keysets []nut02.Keyset,
	proofs cashu.Proofs,
	targetAmount uint64,
	activeKeysetId string,
	spendingCondition *nut10.SpendingCondition,
) (principal preparedOutputs, change preparedOutputs, err error) {
	// decide the shape of the proofs we'll swap for
	proofsAmount := proofs.Amount()
	var (
		principalAmount uint64
		changeAmount    uint64
	)
	fee := calculateFee(proofs, keysets)
	if targetAmount < proofsAmount {
		// we'll get the exact target, then a change, and fee will be taken from the change
		principalAmount = targetAmount
		changeAmount = proofsAmount - targetAmount - fee
	} else if targetAmount == proofsAmount {
		// we're swapping everything, so take the fee from the principal
		principalAmount = targetAmount - fee
		changeAmount = 0
	} else {
		err = fmt.Errorf("can't swap for more than we are sending: %d > %d", targetAmount, proofsAmount)
		return
	}
	splits := make([]uint64, 0, len(proofs)*2)
	splits = append(splits, cashu.AmountSplit(principalAmount)...)
	changeStartIndex := len(splits)
	splits = append(splits, cashu.AmountSplit(changeAmount)...)

	// prepare message to send to mint
	bm, secrets, rs, err := createBlindedMessages(splits, activeKeysetId, spendingCondition)
	if err != nil {
		err = fmt.Errorf("failed to create blinded message: %w", err)
		return
	}

	return preparedOutputs{
			bm:      bm[0:changeStartIndex],
			rs:      rs[0:changeStartIndex],
			secrets: secrets[0:changeStartIndex],
		}, preparedOutputs{
			bm:      bm[changeStartIndex:],
			rs:      rs[changeStartIndex:],
			secrets: secrets[changeStartIndex:],
		}, nil
}
