package projectmetadata

import (
	"context"
	"fmt"
	"io/ioutil"
	"strings"

	log "github.com/Sirupsen/logrus"
	xnetcontext "golang.org/x/net/context"
	"google.golang.org/grpc/codes"

	"code.justin.tv/dta/rockpaperscissors/client/blueprint"
	"code.justin.tv/dta/rockpaperscissors/internal/github"
	"code.justin.tv/dta/rockpaperscissors/internal/sandstorm"
	pb "code.justin.tv/dta/rockpaperscissors/proto"
)

var (
	// ErrSourcePathNotSet is returned when source path isn't set for blueprint
	// ingestion requests.
	ErrSourcePathNotSet = errf(
		codes.InvalidArgument, "Required field source path wasn't set.")
	// ErrGitHubRepositoryHostNotSet is returned when the GitHub repository host
	// isn't given for blueprint ingestion.
	ErrGitHubRepositoryHostNotSet = errf(
		codes.InvalidArgument, "GitHub repository host field not set.")
	// ErrGitHubRepositoryNameNotSet is returned when the GitHub repository name
	// isn't given for blueprint ingestion.
	ErrGitHubRepositoryNameNotSet = errf(
		codes.InvalidArgument, "GitHub repository name field not set.")
)

type gitHubFileGetter struct {
	SandstormClient sandstorm.ClientIface
}

func (c *gitHubFileGetter) GetFileFromGitHub(ctx context.Context, host, name, path string) ([]byte, error) {
	// TODO: instead of fetching the secret and making a new client for every
	// request, we need to, instead, cache a per-host client with an expiration.
	client, err := github.Client(c.SandstormClient, host)
	if err != nil {
		return nil, err
	}

	parts := strings.SplitN(name, "/", 2)
	if len(parts) != 2 {
		return nil, fmt.Errorf("Couldn't split %q into owner and repo parts", name)
	}

	fi, err := client.Repositories.DownloadContents(
		ctx, parts[0], parts[1], path, nil)
	if err != nil {
		// TODO: check for github rate limiting and do a retry-with-backoff
		return nil, err
	}
	defer func() {
		if gitHubCloseErr := fi.Close(); gitHubCloseErr != nil {
			log.Error(gitHubCloseErr)
		}
	}()

	blueprint, err := ioutil.ReadAll(fi)
	return blueprint, err
}

// IngestBlueprint reads a blueprint from some code source, evaluates it, and
// then updates the project metadata.
func (s *Server) IngestBlueprint(ctx xnetcontext.Context, req *pb.IngestBlueprintRequest) (*pb.IngestBlueprintResponse, error) {
	path := req.GetSource().GetPath()
	if path == "" {
		return nil, ErrSourcePathNotSet
	}

	host := req.GetSource().GetRepository().GetGithubRepository().GetHost()
	if host == "" {
		return nil, ErrGitHubRepositoryHostNotSet
	}

	repository := req.GetSource().GetRepository().GetGithubRepository().GetName()
	if repository == "" {
		return nil, ErrGitHubRepositoryNameNotSet
	}

	blueprintBytes, err := s.GitHubFileGetter.GetFileFromGitHub(
		ctx, host, repository, path)
	if err != nil {
		return nil, errf(codes.Internal,
			"Failed to get %s/%s from GitHub: %v", repository, path, err)
	}

	projectMetadata, err := blueprint.Parse(string(blueprintBytes))
	if err != nil {
		return nil, errf(codes.Internal,
			"Failed to parse blueprint %s/%s: %v", repository, path, err)
	}

	projectMetadata.BlueprintLocation = req.GetSource()

	_, err = s.UpdateMetadata(ctx, &pb.UpdateMetadataRequest{
		ProjectMetadata: projectMetadata,
	})
	if err != nil {
		return nil, err
	}
	return &pb.IngestBlueprintResponse{}, nil
}
