package interceptors

import (
	packagev1 "buf.build/gen/go/safedep/api/protocolbuffers/go/safedep/messages/package/v1"
	"github.com/safedep/dry/log"
	"github.com/safedep/pmg/analyzer"
	"github.com/safedep/pmg/guard"
)

// ConfirmationRequest represents a request for user confirmation on a suspicious package
type ConfirmationRequest struct {
	PackageVersion *packagev1.PackageVersion
	AnalysisResult *analyzer.PackageVersionAnalysisResult

	// ResponseChan is used to send the user's response back.
	ResponseChan chan bool
}

// ConfirmationHook is a set of hooks that can be used to customize the confirmation process.
type ConfirmationHook struct {
	// BeforeInteraction is called before the user interaction is started.
	BeforeInteraction func([]*analyzer.PackageVersionAnalysisResult) error

	// AfterInteraction is called after the user interaction is finished.
	AfterInteraction func([]*analyzer.PackageVersionAnalysisResult, bool) error
}

// HandleConfirmationRequests processes confirmation requests sequentially
// This function should be run in a goroutine and will process requests
// from the confirmation channel one at a time, blocking on user input.
//
// The function will exit when the confirmation channel is closed.
func HandleConfirmationRequests(confirmationChan chan *ConfirmationRequest,
	interaction *guard.PackageManagerGuardInteraction, hooks *ConfirmationHook) {
	if hooks == nil {
		hooks = &ConfirmationHook{}
	}

	for req := range confirmationChan {
		func() {
			// The default response is false ie. user did not confirm the installation.
			// The code here falls through and eventually sets this flag to true if user
			// confirms the installation.
			responseVal := false

			// We must make sure to close the response channel to prevent goroutine leaks.
			// Idiomatic go suggests that the writer should close the channel.
			defer func() {
				req.ResponseChan <- responseVal
				close(req.ResponseChan)
			}()

			packageName := req.PackageVersion.GetPackage().GetName()
			log.Debugf("Processing confirmation request for package %s", packageName)

			// Hook to allow the caller to customize the confirmation process.
			// Hook failures are non-fatal and will not break the confirmation process.
			if hooks.BeforeInteraction != nil {
				if err := hooks.BeforeInteraction([]*analyzer.PackageVersionAnalysisResult{req.AnalysisResult}); err != nil {
					log.Errorf("Error before interaction for package %s: %v", packageName, err)
				}
			}

			// Call the user interaction handler to get confirmation
			// This blocks waiting for stdin input
			confirmed, confirmationErr := interaction.GetConfirmationOnMalware([]*analyzer.PackageVersionAnalysisResult{req.AnalysisResult})

			// Must guarantee to call the after interaction hook regardless of the confirmation error.
			if hooks.AfterInteraction != nil {
				if err := hooks.AfterInteraction([]*analyzer.PackageVersionAnalysisResult{req.AnalysisResult}, confirmed); err != nil {
					log.Errorf("Error after interaction for package %s: %v", packageName, err)
				}
			}

			if confirmationErr != nil {
				log.Errorf("Error getting confirmation for package %s: %v", packageName, confirmationErr)
				return
			}

			// Set the response value to the user's confirmation
			responseVal = confirmed
		}()
	}

	log.Debugf("Confirmation handler exiting (channel closed)")
}

// NewConfirmationRequest creates a new confirmation request with a response channel
func NewConfirmationRequest(pkgVersion *packagev1.PackageVersion, result *analyzer.PackageVersionAnalysisResult) *ConfirmationRequest {
	return &ConfirmationRequest{
		PackageVersion: pkgVersion,
		AnalysisResult: result,
		ResponseChan:   make(chan bool, 1),
	}
}
