package main

import (
	"context"
	"encoding/hex"
	"fmt"
	"sync"

	"github.com/callebtc/electronwall/api"
	"github.com/callebtc/electronwall/config"
	"github.com/callebtc/electronwall/rules"
	"github.com/callebtc/electronwall/types"
	"github.com/lightningnetwork/lnd/lnrpc"
	log "github.com/sirupsen/logrus"
)

func (app *App) GetChannelAcceptEvent(ctx context.Context, req lnrpc.ChannelAcceptRequest) (types.ChannelAcceptEvent, error) {
	// print the incoming channel request
	alias, err := app.lnd.getNodeAlias(ctx, hex.EncodeToString(req.NodePubkey))
	if err != nil {
		log.Errorf(err.Error())
	}

	info, err := app.lnd.getNodeInfo(ctx, hex.EncodeToString(req.NodePubkey))
	if err != nil {
		log.Errorf(err.Error())
	}

	noeInfo, err := api.GetApiNodeinfo(hex.EncodeToString(req.NodePubkey))
	if err != nil {
		log.Errorf(err.Error())
	}

	return types.ChannelAcceptEvent{
		PubkeyFrom: hex.EncodeToString(req.NodePubkey),
		AliasFrom:  alias,
		NodeInfo:   info,
		Event:      &req,
		OneMl:      noeInfo.OneMl,
		Amboss:     noeInfo.Amboss,
	}, nil
}

// DispatchChannelAcceptor is the channel acceptor event loop
func (app *App) DispatchChannelAcceptor(ctx context.Context) {
	// the channel event logger
	go func() {
		err := app.logChannelEvents(ctx)
		if err != nil {
			log.Errorf("channel event logger error: %v", err)
		}
	}()

	// the channel event interceptor
	go func() {
		err := app.interceptChannelEvents(ctx)
		if err != nil {
			log.Errorf("channel interceptor error: %v", err)
		}
		// release wait group for channel acceptor
		ctx.Value(ctxKeyWaitGroup).(*sync.WaitGroup).Done()
	}()

	log.Infof("[channel] Listening for incoming channel requests")

}

func (app *App) interceptChannelEvents(ctx context.Context) error {
	// get the lnd grpc connection
	acceptClient, err := app.lnd.channelAcceptor(ctx)
	if err != nil {
		panic(err)
	}
	for {
		req := lnrpc.ChannelAcceptRequest{}
		err = acceptClient.RecvMsg(&req)
		if err != nil {
			return err
		}

		channelAcceptEvent, err := app.GetChannelAcceptEvent(ctx, req)
		if err != nil {
			return err
		}

		var node_info_string string
		if channelAcceptEvent.AliasFrom != "" {
			node_info_string = fmt.Sprintf("%s (%s)", channelAcceptEvent.AliasFrom, hex.EncodeToString(channelAcceptEvent.Event.NodePubkey))
		} else {
			node_info_string = hex.EncodeToString(channelAcceptEvent.Event.NodePubkey)
		}
		log.Debugf("[channel] New channel request from %s", node_info_string)

		var channel_info_string string
		if channelAcceptEvent.AliasFrom != "" {
			channel_info_string = fmt.Sprintf("(%d sat) from %s (%s, %d sat capacity, %d channels)",
				channelAcceptEvent.Event.FundingAmt,
				channelAcceptEvent.AliasFrom,
				trimPubKey(channelAcceptEvent.Event.NodePubkey),
				channelAcceptEvent.NodeInfo.TotalCapacity,
				channelAcceptEvent.NodeInfo.NumChannels,
			)
		} else {
			channel_info_string = fmt.Sprintf("(%d sat) from %s (%d sat capacity, %d channels)",
				channelAcceptEvent.Event.FundingAmt,
				trimPubKey(channelAcceptEvent.Event.NodePubkey),
				channelAcceptEvent.NodeInfo.TotalCapacity,
				channelAcceptEvent.NodeInfo.NumChannels,
			)
		}

		contextLogger := log.WithFields(log.Fields{
			"event":           "channel_request",
			"amount":          channelAcceptEvent.Event.FundingAmt,
			"alias":           channelAcceptEvent.AliasFrom,
			"pubkey":          hex.EncodeToString(channelAcceptEvent.Event.NodePubkey),
			"pending_chan_id": hex.EncodeToString(channelAcceptEvent.Event.PendingChanId),
			"total_capacity":  channelAcceptEvent.NodeInfo.TotalCapacity,
			"num_channels":    channelAcceptEvent.NodeInfo.NumChannels,
		})

		// make decision
		decision_chan := make(chan bool, 1)
		rules_decision, err := rules.Apply(channelAcceptEvent, decision_chan)
		if err != nil {
			return err
		}
		// parse list
		list_decision, err := app.channelAcceptListDecision(req)
		if err != nil {
			return err
		}

		accept := true
		if !rules_decision || !list_decision {
			accept = false
		}

		res := lnrpc.ChannelAcceptResponse{}
		if accept {
			if config.Configuration.LogJson {
				contextLogger.Infof("allow")
			} else {
				log.Infof("[channel] ✅ Allow channel %s", channel_info_string)
			}
			res = lnrpc.ChannelAcceptResponse{Accept: true,
				PendingChanId:   req.PendingChanId,
				CsvDelay:        req.CsvDelay,
				MaxHtlcCount:    req.MaxAcceptedHtlcs,
				ReserveSat:      req.ChannelReserve,
				InFlightMaxMsat: req.MaxValueInFlight,
				MinHtlcIn:       req.MinHtlc,
			}

		} else {
			if config.Configuration.LogJson {
				contextLogger.Infof("deny")
			} else {
				log.Infof("[channel] ❌ Deny channel %s", channel_info_string)
			}
			res = lnrpc.ChannelAcceptResponse{Accept: false,
				Error: config.Configuration.ChannelRejectMessage}
		}
		err = acceptClient.Send(&res)
		if err != nil {
			log.Errorf(err.Error())
		}
	}

}

func (app *App) channelAcceptListDecision(req lnrpc.ChannelAcceptRequest) (bool, error) {
	// determine mode and list of channels to parse
	var accept bool
	var listToParse []string
	if config.Configuration.ChannelMode == "allowlist" {
		accept = false
		listToParse = config.Configuration.ChannelAllowlist
	} else if config.Configuration.ChannelMode == "denylist" {
		accept = true
		listToParse = config.Configuration.ChannelDenylist
	}

	// parse and make decision
	for _, pubkey := range listToParse {
		if hex.EncodeToString(req.NodePubkey) == pubkey || pubkey == "*" {
			accept = !accept
			break
		}
	}
	log.Infof("[list] decision: %t", accept)
	return accept, nil

}

func (app *App) logChannelEvents(ctx context.Context) error {
	stream, err := app.lnd.subscribeChannelEvents(ctx, &lnrpc.ChannelEventSubscription{})
	if err != nil {
		return err
	}
	for {
		event, err := stream.Recv()
		if err != nil {
			return err
		}
		switch event.Type {
		case lnrpc.ChannelEventUpdate_OPEN_CHANNEL:
			alias, err := app.lnd.getNodeAlias(ctx, event.GetOpenChannel().RemotePubkey)
			if err != nil {
				log.Errorf(err.Error())
				alias = trimPubKey([]byte(event.GetOpenChannel().RemotePubkey))
			}
			channel_info_string := fmt.Sprintf("(%d sat) from %s",
				event.GetOpenChannel().Capacity,
				alias,
			)

			if config.Configuration.LogJson {
				contextLogger := log.WithFields(log.Fields{
					"event":    "channel",
					"capacity": event.GetOpenChannel().Capacity,
					"alias":    alias,
					"pubkey":   event.GetOpenChannel().RemotePubkey,
					"chan_id":  ParseChannelID(event.GetOpenChannel().ChanId),
				})
				contextLogger.Infof("open")
			} else {
				log.Infof("[channel] Opened channel %s %s", ParseChannelID(event.GetOpenChannel().ChanId), channel_info_string)
			}
		}
		log.Tracef("[channel] Event: %s", event.String())
	}
}
