package ingestqueueconsumer

import (
	"context"
	"strings"

	"github.com/golang/protobuf/proto"
	gh "github.com/google/go-github/github"
	"github.com/pkg/errors"
	xnetcontext "golang.org/x/net/context"
	"google.golang.org/grpc"

	"code.justin.tv/dta/rockpaperscissors/client/eventpublisher"
	"code.justin.tv/dta/rockpaperscissors/internal/github"
	"code.justin.tv/dta/rockpaperscissors/internal/sandstorm"
	pb "code.justin.tv/dta/rockpaperscissors/proto"
	eventspb "code.justin.tv/dta/rockpaperscissors/proto/events"
)

const statsEventType = "GitHub-Stats"

type eventServiceIface interface {
	AddEvent(xnetcontext.Context, *pb.AddEventRequest, ...grpc.CallOption) (*pb.AddEventResponse, error)
}

// GitHubStatsIngestor handles IngestGitHubStatsRequest to create GitHub-Stats events from GitHub pulls.
type GitHubStatsIngestor struct {
	eventClient     eventServiceIface
	sandstormClient sandstorm.ClientIface
	gitHubClients   map[string]*gh.Client
}

// NewGitHubStatsIngestor initializes a GitHubStatsIngestor struct.
func NewGitHubStatsIngestor(eventClient eventServiceIface, sandstormClient sandstorm.ClientIface) *GitHubStatsIngestor {
	return &GitHubStatsIngestor{
		eventClient:     eventClient,
		sandstormClient: sandstormClient,
		gitHubClients:   make(map[string]*gh.Client),
	}
}

func (c *GitHubStatsIngestor) getGitHubClient(repoHost string) (*gh.Client, error) {
	client := c.gitHubClients[repoHost]
	if client == nil {
		var err error
		client, err = github.Client(c.sandstormClient, repoHost)
		if err != nil {
			return nil, err
		}
		c.gitHubClients[repoHost] = client
	}
	return client, nil
}

// Ingest handles a IngestGitHubStatsRequest. Talks to GitHub and creates GitHub-Stats events in datastore.
func (c *GitHubStatsIngestor) Ingest(ctx context.Context, in *pb.IngestGitHubStatsRequest) error {
	gitHubClient, err := c.getGitHubClient(in.GithubRepository.GetHost())
	if err != nil {
		return err
	}

	parts := strings.SplitN(in.GithubRepository.GetName(), "/", 2)
	if len(parts) != 2 {
		return errors.Errorf(
			"repo_name \"%s\" isn't in correct format", in.GithubRepository.GetName())
	}
	repoOwner := parts[0]
	repoName := parts[1]

	commit, resp, err := gitHubClient.Repositories.GetCommit(
		ctx, repoOwner, repoName, in.CommitSha)
	if err != nil {
		if resp != nil && resp.StatusCode == 404 {
			// TODO: should we some a retry for a bit for 404s?
			return nil
		}
		return errors.Wrapf(err, "Failed to get commit %s from %s/%s",
			in.CommitSha, repoOwner, repoName)
	}

	stats := &eventspb.GitHubCommitStatsEvent{
		GithubRepository: in.GithubRepository,
		CommitSha:        in.CommitSha,
		Additions:        uint32(*commit.Stats.Additions),
		Deletions:        uint32(*commit.Stats.Deletions),
		Total:            uint32(*commit.Stats.Total),
	}

	statsEventBody, err := proto.Marshal(stats)
	if err != nil {
		return errors.Wrap(err, "Failed to marshal GitHubCommitStatsEvent")
	}

	attributes := map[string]string{
		"github_repository": "https://" + in.GithubRepository.GetHost() + "/" + in.GithubRepository.GetName(),
	}
	if commit.Author != nil {
		attributes["author"] = *commit.Author.Login
	}
	if commit.Committer != nil {
		attributes["committer"] = *commit.Committer.Login
	}

	event, err := eventpublisher.CreateEvent(
		*commit.Commit.Committer.Date, statsEventType, statsEventBody, attributes)
	if err != nil {
		return err
	}

	_, err = c.eventClient.AddEvent(ctx, &pb.AddEventRequest{
		Event: event,
	})
	if err != nil {
		return errors.Wrapf(err, "Failed to add %s event", event.GetType())
	}

	return nil
}
