package projectmetadata

import (
	"context"

	"github.com/golang/protobuf/proto"
	"google.golang.org/grpc"

	pb "code.justin.tv/dta/rockpaperscissors/proto"
)

// Client is a client for the ProjectMetadataService gRPC service.
type Client struct {
	conn   *grpc.ClientConn
	client pb.ProjectMetadataServiceClient
}

// NewClient is a constructor for the client. Please call Close() when done.
func NewClient(address string) (*Client, error) {
	conn, err := grpc.Dial(address, grpc.WithInsecure())
	if err != nil {
		return nil, err
	}

	return &Client{
		conn:   conn,
		client: pb.NewProjectMetadataServiceClient(conn),
	}, nil
}

// Close closes the client connection.
func (c *Client) Close() error {
	return c.conn.Close()
}

// ListProjects returns a list of ProjectMetadata protobufs with ID and name.
func (c *Client) ListProjects(includeArchived bool) ([]*pb.ProjectMetadata, error) {
	response, err := c.client.ListProjects(
		context.Background(),
		&pb.ListProjectsRequest{
			IncludeArchived: includeArchived,
		},
	)
	if err != nil {
		return nil, err
	}
	return response.GetProjects(), nil
}

// GetMetadata simply returns the the given ProjectMetadata protobufs from the datastore.
func (c *Client) GetMetadata(projectID string) (*pb.ProjectMetadata, error) {
	response, err := c.client.GetMetadata(
		context.Background(),
		&pb.GetMetadataRequest{
			ProjectId: projectID,
		},
	)
	if err != nil {
		return nil, err
	}
	return response.Project, nil
}

// UpdateMetadata updates the given ProjectMetadata protobuf in the datastore.
func (c *Client) UpdateMetadata(projectMetadata *pb.ProjectMetadata) error {
	_, err := c.client.UpdateMetadata(
		context.Background(),
		&pb.UpdateMetadataRequest{ProjectMetadata: projectMetadata},
	)
	if err != nil {
		return err
	}
	return nil
}

// IngestBlueprint pulls a blueprint from GitHub and ingests it to update project metadata.
//
// Passing an empty string as the host will have it use the default GitHub host.
func (c *Client) IngestBlueprint(repo string, path string, host string) error {
	githubRepository := &pb.GitHubRepository{
		Name: proto.String(repo),
	}

	if len(host) > 0 {
		githubRepository.Host = proto.String(host)
	}

	req := &pb.IngestBlueprintRequest{
		Source: &pb.SourceFilePath{
			Repository: &pb.SourceRepository{
				Repository: &pb.SourceRepository_GithubRepository{
					GithubRepository: githubRepository,
				},
			},
			Path: proto.String(path),
		},
	}

	_, err := c.client.IngestBlueprint(context.Background(), req)
	if err != nil {
		return err
	}
	return nil
}

// ListTeams returns a map of team names with lists of projects in that team.
func (c *Client) ListTeams(includeArchived bool) (map[string][]*pb.ProjectMetadata, error) {
	response, err := c.client.ListTeams(
		context.Background(),
		&pb.ListTeamsRequest{
			IncludeArchived: includeArchived,
		},
	)
	if err != nil {
		return nil, err
	}

	teamMap := make(map[string][]*pb.ProjectMetadata)
	for _, entry := range response.GetTeams() {
		teamMap[entry.TeamName] = entry.GetProjects()
	}
	return teamMap, nil
}

// GetTeam returns details about a team including the projects in it.
func (c *Client) GetTeam(teamName string) ([]*pb.ProjectMetadata, error) {
	response, err := c.client.GetTeam(
		context.Background(),
		&pb.GetTeamRequest{
			TeamName: teamName,
		},
	)
	if err != nil {
		return nil, err
	}
	return response.GetProjects(), nil
}

// ListOrgs returns a map of org names with of projects in that team.
func (c *Client) ListOrgs(includeArchived bool) (map[string]map[string][]*pb.ProjectMetadata, error) {
	response, err := c.client.ListOrgs(
		context.Background(),
		&pb.ListOrgsRequest{
			IncludeArchived: includeArchived,
			IncludeProjects: true,
		},
	)
	if err != nil {
		return nil, err
	}

	orgsMap := make(map[string]map[string][]*pb.ProjectMetadata)
	for _, orgEntry := range response.GetOrgs() {
		if orgsMap[orgEntry.OrgName] == nil {
			orgsMap[orgEntry.OrgName] = make(map[string][]*pb.ProjectMetadata)
		}
		for _, teamEntry := range orgEntry.GetTeams() {
			orgsMap[orgEntry.OrgName][teamEntry.TeamName] = teamEntry.GetProjects()
		}
	}
	return orgsMap, nil
}

// GetOrg returns details about a org including the teams and projects in it.
func (c *Client) GetOrg(orgName string) (map[string][]*pb.ProjectMetadata, error) {
	response, err := c.client.GetOrg(
		context.Background(),
		&pb.GetOrgRequest{
			OrgName:         orgName,
			IncludeProjects: true,
		},
	)
	if err != nil {
		return nil, err
	}

	teamsMap := make(map[string][]*pb.ProjectMetadata)
	for _, teamEntry := range response.GetTeams() {
		teamsMap[teamEntry.TeamName] = teamEntry.GetProjects()
	}
	return teamsMap, nil
}
