package samples

import (
	"context"
	"fmt"
	"os"
	"os/signal"

	log "github.com/sirupsen/logrus"

	"github.com/stripe/stripe-cli/pkg/validators"

	"github.com/spf13/afero"

	"github.com/go-git/go-git/v5"
)

// CreationStatus is the current step in the sample creation routine
type CreationStatus int

const (
	// WillInitialize means this sample will be initialized
	WillInitialize CreationStatus = iota

	// DidInitialize means this sample has finished initializing
	DidInitialize

	// WillCopy means the downloaded sample will be copied to the target path
	WillCopy

	// DidCopy means the downloaded sample has finished being copied to the target path
	DidCopy

	// WillConfigure means the .env of the sample will be configured with the user's Stripe account details
	WillConfigure

	// DidConfigure means the .env of the sample has finished being configured with the user's Stripe account details
	DidConfigure

	// DidConfigureWithoutTestPubKey means the .env of the sample has finished being configured without the publishable key
	DidConfigureWithoutTestPubKey

	// Done means sample creation is complete
	Done
)

// CreationResult is the return value sent over a channel
type CreationResult struct {
	State       CreationStatus
	Path        string
	PostInstall string
	Err         error
}

// Create creates a sample at a destination with the selected integration, client language, and server language
func (sampleManager *SampleManager) Create(
	ctx context.Context,
	sampleName string,
	selectedConfig *SelectedConfig,
	destination string,
	forceRefresh bool,
	resultChan chan<- CreationResult,
) {
	defer close(resultChan)

	cacheFolder, err := sampleManager.appCacheFolder("samples-list")
	if err != nil {
		logger := log.Logger{
			Out: os.Stdout,
		}

		logger.WithFields(log.Fields{
			"prefix": "samples.create.cacheFolder",
			"error":  err,
		}).Debug("Could not create cacheFolder for samples")
	}
	sampleManager.SampleLister = newCachedGithubSampleLister(sampleManager, sampleListGithubURL, cacheFolder)

	exists, _ := afero.DirExists(sampleManager.Fs, destination)
	if exists {
		resultChan <- CreationResult{Err: fmt.Errorf("Path already exists for: %s", destination)}
		return
	}

	if forceRefresh {
		err := sampleManager.DeleteCache(sampleName)
		if err != nil {
			logger := log.Logger{
				Out: os.Stdout,
			}

			logger.WithFields(log.Fields{
				"prefix": "samples.create.forceRefresh",
				"error":  err,
			}).Debug("Could not clear cache")
		}
	}

	resultChan <- CreationResult{State: WillInitialize}

	// Initialize the selected sample in the local cache directory.
	// This will either clone or update the specified sample,
	// depending on whether or not it's. Additionally, this
	// identifies if the sample has multiple integrations and what
	// languages it supports.
	err = sampleManager.Initialize(sampleName)
	if err != nil {
		switch e := err.Error(); e {
		case git.NoErrAlreadyUpToDate.Error():
			// Repo is already up to date. This isn't a program
			// error to continue as normal
			break
		case git.ErrRepositoryAlreadyExists.Error():
			// If the repository already exists and we don't pull
			// for some reason, that's fine as we can use the existing
			// repository
			break
		default:
			resultChan <- CreationResult{Err: err}
			return
		}
	}

	resultChan <- CreationResult{State: DidInitialize}

	sampleManager.SelectedConfig = *selectedConfig

	// Setup to intercept ctrl+c
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)

	go func() {
		<-c
		sampleManager.Cleanup(sampleName)
		os.Exit(1)
	}()

	resultChan <- CreationResult{State: WillCopy}

	// Create the target folder to copy the sample in to. We do
	// this here in case any of the steps above fail, minimizing
	// the change that we create a dangling empty folder
	targetPath, err := sampleManager.MakeFolder(destination)
	if err != nil {
		resultChan <- CreationResult{Err: err}
		return
	}

	// Perform the copy of the sample given the selected options
	// from the selections above
	err = sampleManager.Copy(targetPath)
	if err != nil {
		resultChan <- CreationResult{Err: err}
		return
	}

	resultChan <- CreationResult{State: DidCopy}

	resultChan <- CreationResult{State: WillConfigure}

	err = sampleManager.WriteDotEnv(ctx, targetPath)
	if err != nil {
		if err == validators.ErrPubKeyNotConfigured {
			resultChan <- CreationResult{State: DidConfigureWithoutTestPubKey}
		} else {
			resultChan <- CreationResult{Err: err}
			return
		}
	} else {
		resultChan <- CreationResult{State: DidConfigure}
	}

	resultChan <- CreationResult{State: Done, Path: targetPath, PostInstall: sampleManager.PostInstall()}
}
