package main

import (
	"context"
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/lightninglabs/loop/instantout/reservation"
	"github.com/lightninglabs/loop/looprpc"
	"github.com/urfave/cli/v3"
)

var instantOutCommand = &cli.Command{
	Name:  "instantout",
	Usage: "perform an instant off-chain to on-chain swap (looping out)",
	Description: `
	Attempts to instantly loop out into the backing lnd's wallet. The amount
	will be chosen via the cli.
	`,
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name: "channel",
			Usage: "the comma-separated list of short " +
				"channel IDs of the channels to loop out",
		},
		&cli.StringFlag{
			Name: "addr",
			Usage: "the optional address that the looped out funds " +
				"should be sent to, if let blank the funds " +
				"will go to lnd's wallet",
		},
	},
	Action: instantOut,
}

func instantOut(ctx context.Context, cmd *cli.Command) error {
	// Parse outgoing channel set. Don't string split if the flag is empty.
	// Otherwise, strings.Split returns a slice of length one with an empty
	// element.
	var outgoingChanSet []uint64
	if cmd.IsSet("channel") {
		chanStrings := strings.Split(cmd.String("channel"), ",")
		for _, chanString := range chanStrings {
			chanID, err := strconv.ParseUint(chanString, 10, 64)
			if err != nil {
				return fmt.Errorf("error parsing channel id "+
					"\"%v\"", chanString)
			}
			outgoingChanSet = append(outgoingChanSet, chanID)
		}
	}

	// First set up the swap client itself.
	client, cleanup, err := getClient(cmd)
	if err != nil {
		return err
	}
	defer cleanup()

	// Now we fetch all the confirmed reservations.
	reservations, err := client.ListReservations(
		ctx, &looprpc.ListReservationsRequest{},
	)
	if err != nil {
		return err
	}

	var (
		confirmedReservations []*looprpc.ClientReservation
		totalAmt              int64
		idx                   int
	)

	for _, res := range reservations.Reservations {
		if res.State != string(reservation.Confirmed) {
			continue
		}

		confirmedReservations = append(confirmedReservations, res)
	}

	if len(confirmedReservations) == 0 {
		fmt.Printf("No confirmed reservations found \n")
		return nil
	}

	fmt.Printf("Available reservations: \n\n")
	for _, res := range confirmedReservations {
		idx++
		fmt.Printf("Reservation %v: shortid %x, amt %v, expiry "+
			"height %v \n", idx, res.ReservationId[:3], res.Amount,
			res.Expiry)

		totalAmt += int64(res.Amount)
	}

	fmt.Println()
	fmt.Printf("Max amount to instant out: %v\n", totalAmt)
	fmt.Println()

	fmt.Println("Select reservations for instantout (e.g. '1,2,3')")
	fmt.Println("Type 'ALL' to use all available reservations.")

	var answer string
	fmt.Scanln(&answer)

	// Parse
	var (
		selectedReservations [][]byte
		selectedAmt          uint64
	)
	switch answer {
	case "ALL":
		for _, res := range confirmedReservations {
			selectedReservations = append(
				selectedReservations,
				res.ReservationId,
			)
			selectedAmt += res.Amount
		}

	case "":
		return fmt.Errorf("no reservations selected")

	default:
		selectedIndexes := strings.Split(answer, ",")
		selectedIndexMap := make(map[int]struct{})
		for _, idxStr := range selectedIndexes {
			idx, err := strconv.Atoi(idxStr)
			if err != nil {
				return err
			}
			if idx < 0 {
				return fmt.Errorf("invalid index %v", idx)
			}

			if idx > len(confirmedReservations) {
				return fmt.Errorf("invalid index %v", idx)
			}
			if _, ok := selectedIndexMap[idx]; ok {
				return fmt.Errorf("duplicate index %v", idx)
			}

			selectedReservations = append(
				selectedReservations,
				confirmedReservations[idx-1].ReservationId,
			)

			selectedIndexMap[idx] = struct{}{}
			selectedAmt += confirmedReservations[idx-1].Amount
		}
	}

	// Now that we have the selected reservations we can estimate the
	// fee-rates.
	quote, err := client.InstantOutQuote(
		ctx, &looprpc.InstantOutQuoteRequest{
			Amt:            selectedAmt,
			ReservationIds: selectedReservations,
		},
	)
	if err != nil {
		return err
	}
	fmt.Println()
	fmt.Printf(satAmtFmt, "Estimated on-chain fee:", quote.SweepFeeSat)
	fmt.Printf(satAmtFmt, "Service fee:", quote.ServiceFeeSat)
	fmt.Println()

	fmt.Printf("CONTINUE SWAP? (y/n): ")

	fmt.Scanln(&answer)
	if answer != "y" {
		return errors.New("swap canceled")
	}

	fmt.Println("Starting instant swap out")

	// Now we can request the instant out swap.
	instantOutRes, err := client.InstantOut(
		ctx,
		&looprpc.InstantOutRequest{
			ReservationIds:  selectedReservations,
			OutgoingChanSet: outgoingChanSet,
			DestAddr:        cmd.String("addr"),
		},
	)
	if err != nil {
		return err
	}

	fmt.Printf("Instant out swap initiated with ID: %x, State: %v \n",
		instantOutRes.InstantOutHash, instantOutRes.State)

	if instantOutRes.SweepTxId != "" {
		fmt.Printf("Sweepless sweep tx id: %v \n",
			instantOutRes.SweepTxId)
	}

	return nil
}

var listInstantOutsCommand = &cli.Command{
	Name:  "listinstantouts",
	Usage: "list all instant out swaps",
	Description: `
	List all instant out swaps.
	`,
	Action: listInstantOuts,
}

func listInstantOuts(ctx context.Context, cmd *cli.Command) error {
	// First set up the swap client itself.
	client, cleanup, err := getClient(cmd)
	if err != nil {
		return err
	}
	defer cleanup()

	resp, err := client.ListInstantOuts(
		ctx, &looprpc.ListInstantOutsRequest{},
	)
	if err != nil {
		return err
	}

	printRespJSON(resp)

	return nil
}
