/*-
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 69):
 * <syrinx@FreeBSD.org> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.   -Shteryana Shopova
 * ----------------------------------------------------------------------------
 */

package main

import (
	"bytes"
	"context"
	"fmt"
	"github.com/akamensky/argparse"
	"github.com/google/go-github/github" // with go modules disabled
	"golang.org/x/oauth2"
	"io/ioutil"
	"os"
)

func main() {

	parser := argparse.NewParser("sshkeys", "Fetch SSH keys for a Github team members")
	authToken := parser.String("a", "authtoken", &argparse.Options{Required: false, Help: "Github Auth token", Default: AuthToken})
	keysDir := parser.String("d", "directory", &argparse.Options{Required: false, Help: "Path where to store the key files", Default: "./"})
	ghOrganization := parser.String("o", "org", &argparse.Options{Required: false, Help: "Github Organization name", Default: "OpenFest"})
	fetchPgp := parser.Flag("p", "pgp-keys", &argparse.Options{Required: false, Help: "Fetch configured PGP key ids", Default: false})
	quiet := parser.Flag("q", "quiet", &argparse.Options{Required: false, Help: "Skip output to stdout", Default: false})
	ghTeam := parser.String("t", "team", &argparse.Options{Required: false, Help: "Github Team name, 'all' for all members of the organization", Default: "NOC"})
	verbose := parser.Flag("v", "verbose", &argparse.Options{Required: false, Help: "Verbose output: print keys to stdout", Default: false})

	// Parse input
	err := parser.Parse(os.Args)
	if err != nil {
		fmt.Print(parser.Usage(err))
		os.Exit(1)
	}

	if *verbose == false {
		fi, err := os.Lstat(*keysDir)
		if err != nil {
			fmt.Println(*keysDir, ": target directory error :", err)
			os.Exit(1)
		} else {
			if fi.Mode().IsDir() == false {
				fmt.Println(*keysDir, ": target directory error : not a directory - ", fi.Mode())
				os.Exit(1)
			}
		}
	}

	ctx := context.Background()
	ts := oauth2.StaticTokenSource(
		&oauth2.Token{AccessToken: *authToken},
	)
	tc := oauth2.NewClient(ctx, ts)

	client := github.NewClient(tc)

	teamMembers := fetchUsers(client, ghOrganization, ghTeam)
	for _, user := range teamMembers {
		if *quiet == false {
			fmt.Println("Fetching keys for", *user)
		}
		var sshKeys bytes.Buffer
		var pgpKeys bytes.Buffer

		for nextPage := 0; ; {

			// list all teams an org for the current user
			opt := &github.ListOptions{nextPage, 50}
			keys, rsp, err := client.Users.ListKeys(ctx, *user, opt)

			if err != nil {
				fmt.Println("client.Users.ListKeys error: ", err)
				os.Exit(-1)
			}

			if rsp == nil {
				fmt.Println("Users.ListKeys returned empty response: ", err)
			}

			for _, key := range keys {
				if *verbose == true {
					fmt.Println(*key.Key)
				}
				sshKeys.WriteString(*key.Key)
				sshKeys.WriteString("\n")
			}

			if rsp.NextPage == 0 || nextPage == rsp.NextPage {
				break
			}
			nextPage = rsp.NextPage
		}

		if *quiet == false && *verbose == false {
			fmt.Println("Writing to", *keysDir+"/"+*user+".key")
		}
		err := ioutil.WriteFile(*keysDir+"/"+*user+".key", sshKeys.Bytes(), 0444)
		if err != nil && *verbose == false {
			fmt.Println(*user+".key error ", err)
		}

		if *fetchPgp == true {
			for nextPage := 0; ; {

				// list all teams an org for the current user
				opt := &github.ListOptions{nextPage, 50}
				keys, rsp, err := client.Users.ListGPGKeys(ctx, *user, opt)

				if err != nil {
					fmt.Println("client.Users.ListGPGKeys error: ", err)
					os.Exit(-1)
				}

				if rsp == nil {
					fmt.Println("Users.ListGPGKeys returned empty response: ", err)
				}

				for _, key := range keys {
					if *verbose == true {
						fmt.Println(*key.KeyID)
					}
					pgpKeys.WriteString(*key.KeyID)
					pgpKeys.WriteString("\n")
				}

				if rsp.NextPage == 0 || nextPage == rsp.NextPage {
					break
				}
				nextPage = rsp.NextPage
			}
			if *quiet == false && *verbose == false {
				fmt.Println("Writing to", *keysDir+"/"+*user+".gpg")
			}
			err = ioutil.WriteFile(*keysDir+"/"+*user+".gpg", pgpKeys.Bytes(), 0444)
			if err != nil && *verbose == false {
				fmt.Println(*user+".gpg error ", err)
			}
		}
	}

	os.Exit(0)
}

func fetchUsers(client *github.Client, org *string, team *string) (teamMembers []*string) {
	var targetTeam *github.Team

	if team == nil || *team == "all" {
		for nextPage := 0; ; {
			// list all members for the given organization's team
			opt := &github.ListMembersOptions{
				PublicOnly:  false,
				ListOptions: github.ListOptions{nextPage, 50},
			}

			users, rsp, err := client.Organizations.ListMembers(context.Background(), *org, opt)

			if err != nil {
				fmt.Println("client.Organizations.ListMembers ", err)
				os.Exit(-1)
			}

			if rsp == nil {
				fmt.Println("client.Organizations.ListMembers: ", err)
			}

			for _, user := range users {
				teamMembers = append(teamMembers, user.Login)
			}

			if rsp.NextPage == 0 || nextPage == rsp.NextPage {
				break
			}
			nextPage = rsp.NextPage
		}
	} else {
		for nextPage := 0; ; {
			// list all teams for the specified org
			opt := &github.ListOptions{nextPage, 50}
			teams, rsp, err := client.Teams.ListTeams(context.Background(), *org, opt)

			if err != nil {
				fmt.Println("client.ListTeams error: ", err)
				os.Exit(-1)
			}

			if rsp == nil {
				fmt.Println("client.ListTeams returned empty response: ", err)
			}

			for _, ghTeam := range teams {
				if *ghTeam.Name == *team {
					targetTeam = ghTeam
					break
				}
			}

			if rsp.NextPage == 0 || nextPage == rsp.NextPage {
				break
			}
			nextPage = rsp.NextPage
		}

		if targetTeam == nil {
			fmt.Println(*team, " team not found in ", *org)
			os.Exit(2)
		}

		for nextPage := 0; ; {
			// list all members for the given organization's team
			opt := &github.TeamListTeamMembersOptions{
				Role:        "all",
				ListOptions: github.ListOptions{nextPage, 50},
			}

			users, rsp, err := client.Teams.ListTeamMembers(context.Background(), *targetTeam.ID, opt)

			if err != nil {
				fmt.Println("client.Teams.ListTeamMembers ", err)
				os.Exit(-1)
			}

			if rsp == nil {
				fmt.Println("client.Teams.ListTeamMembers: ", err)
			}

			for _, user := range users {
				teamMembers = append(teamMembers, user.Login)
			}

			if rsp.NextPage == 0 || nextPage == rsp.NextPage {
				break
			}
			nextPage = rsp.NextPage
		}
	}

	return teamMembers
}