// Copyright 2019 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//	https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	pb "pigweed/proto/pw_target_runner/target_runner_pb"
)

// Client is a gRPC client that communicates with a TargetRunner service.
type Client struct {
	conn *grpc.ClientConn
}

// NewClient creates a gRPC client which connects to a gRPC server hosted at the
// specified address.
func NewClient(host string, port int) (*Client, error) {
	// The server currently only supports running locally over an insecure
	// connection.
	// TODO(frolv): Investigate adding TLS support to the server and client.
	opts := []grpc.DialOption{
		grpc.WithInsecure(),
		grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(20 * 1024 * 1024)),
	}

	conn, err := grpc.Dial(fmt.Sprintf("%s:%d", host, port), opts...)
	if err != nil {
		return nil, err
	}

	return &Client{conn}, nil
}

// RunBinary sends a RunBinary RPC to the target runner service.
func (c *Client) RunBinary(path string) error {
	data, err := os.ReadFile(path)
	if err != nil {
		return err
	}

	client := pb.NewTargetRunnerClient(c.conn)
	req := &pb.RunBinaryRequest{Binary: &pb.RunBinaryRequest_TestBinary{TestBinary: data}}

	res, err := client.RunBinary(context.Background(), req)
	if err != nil {
		return err
	}

	fmt.Printf("%s\n", path)
	fmt.Printf(
		"Queued for %v, ran in %v\n\n",
		time.Duration(res.QueueTimeNs),
		time.Duration(res.RunTimeNs),
	)
	fmt.Println(string(res.Output))

	if res.Result != pb.RunStatus_SUCCESS {
		return errors.New("Binary run was unsuccessful")
	}

	return nil
}

func main() {
	hostPtr := flag.String("host", "localhost", "Server host")
	portPtr := flag.Int("port", 8080, "Server port")
	pathPtr := flag.String("binary", "", "Path to executable file")
	serverSuggestionPtr := flag.String("server_suggestion", "", "Suggested command to display if no server is available.")

	flag.Parse()

	if *pathPtr == "" {
		log.Fatalf("Must provide -binary option")
	}

	cli, err := NewClient(*hostPtr, *portPtr)
	if err != nil {
		log.Fatalf("Failed to create gRPC client: %v", err)
	}

	if err := cli.RunBinary(*pathPtr); err != nil {
		log.Println("Failed to run executable on target:")
		log.Println("")

		s, _ := status.FromError(err)
		if s.Code() == codes.Unavailable {
			log.Println("  No pw_target_runner_server is running.")
			log.Println("  Check that a server has been started for your target.")
			if *serverSuggestionPtr != "" {
				log.Println("  Consider starting a test server with: ", *serverSuggestionPtr)
			}
		} else {
			log.Printf("  %v\n", err)
		}

		log.Fatal("")
	}
}
