// Copyright 2022 Google LLC
//
// 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 exists for running host orchestrator(HO) server.
package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"os/exec"
	"os/user"
	"path/filepath"
	"sync"
	"time"

	"github.com/google/android-cuttlefish/frontend/src/host_orchestrator/orchestrator"
	"github.com/google/android-cuttlefish/frontend/src/host_orchestrator/orchestrator/debug"
	hoexec "github.com/google/android-cuttlefish/frontend/src/host_orchestrator/orchestrator/exec"
	"github.com/gorilla/mux"
)

const (
	DefaultListenAddress = "127.0.0.1"
)

func defaultCVDArtifactsDir() string {
	u, err := user.Current()
	if err != nil {
		log.Fatalf("Unable to get current user uid: %v", err)
	}
	return fmt.Sprintf("/tmp/cvd/%s/artifacts", u.Uid)
}

func startHttpServer(addr string, port int) error {
	log.Printf("Host Orchestrator is listening at http://%s:%d", addr, port)

	// handler is nil, so DefaultServeMux is used.
	return http.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), nil)
}

// Logs the version of relevant HO service dependencies.
func logDepsVersions() {
	var deps = []struct {
		name string
		cmd  string
		args []string
	}{
		{
			name: "kernel",
			cmd:  "uname",
			args: []string{"-r"},
		},
		{
			name: "cvd",
			cmd:  "cvd",
			args: []string{"version"},
		},
	}
	for _, e := range deps {
		out, err := hoexec.Exec(exec.CommandContext, e.cmd, e.args...)
		if err != nil {
			log.Printf("%q version error: %+v\n\n", e.name, err)
		} else {
			log.Printf("%s version\n%s\n", e.name, out)
		}
	}
}

func start(starters []func() error) {
	wg := new(sync.WaitGroup)
	wg.Add(len(starters))
	for _, starter := range starters {
		go func(f func() error) {
			defer wg.Done()
			if err := f(); err != nil {
				log.Fatal(err)
			}
		}(starter)
	}
	wg.Wait()
}

func newOperatorProxy(port int) *httputil.ReverseProxy {
	if port <= 0 {
		log.Fatal("The host orchestrator requires access to the operator")
	}
	operatorURL, err := url.Parse(fmt.Sprintf("http://127.0.0.1:%d", port))
	if err != nil {
		log.Fatalf("Invalid operator port (%d): %v", port, err)
	}
	proxy := httputil.NewSingleHostReverseProxy(operatorURL)
	proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
		log.Printf("request %q failed: proxy error: %v", r.Method+" "+r.URL.Path, err)
		w.Header().Add("x-cutf-proxy", "ho-operator")
		w.WriteHeader(http.StatusBadGateway)
	}
	return proxy
}

//	@Title		Cuttlefish Host Orchestrator API
//	@Version	1.0

//	@license.name	Apache 2.0
//	@license.url	https://www.apache.org/licenses/LICENSE-2.0

//	@BasePath	/

func main() {
	httpPort := flag.Int("http_port", 2080, "Port to listen on for HTTP requests.")
	operatorPort := flag.Int("operator_http_port", 1080, "Port where the operator is listening.")
	abURL := flag.String("android_build_url", "", "URL to an Android Build API.")
	imRootDir := flag.String("cvd_artifacts_dir", defaultCVDArtifactsDir(), "Directory where cvd will download android build artifacts to.")
	address := flag.String("listen_addr", DefaultListenAddress, "IP address to listen for requests.")
	logFile := flag.String("log_file", "", "Path to file to write logs to.")
	buildAPICredsUseGCEMetadata := flag.Bool("build_api_credentials_use_gce_metadata", false, "Indicates whether to use the GCE metadata to get the Build API credentials")

	flag.Parse()

	if *logFile != "" {
		f, err := os.OpenFile(*logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
		if err != nil {
			log.Fatalf("error opening log file %q: %v", *logFile, err)
		}
		defer f.Close()
		w := io.MultiWriter(os.Stderr, f)
		log.SetOutput(w)
	}

	logDepsVersions()

	if err := os.MkdirAll(*imRootDir, 0774); err != nil {
		log.Fatalf("Unable to create artifacts directory: %v", err)
	}

	imPaths := orchestrator.IMPaths{
		CVDBugReportsDir:    filepath.Join(*imRootDir, "cvdbugreports"),
		SnapshotsRootDir:    filepath.Join(*imRootDir, "snapshots"),
		ImageDirectoriesDir: filepath.Join(*imRootDir, "image_dirs"),
	}

	om := orchestrator.NewMapOM()
	uamOpts := orchestrator.UserArtifactsManagerOpts{
		RootDir: filepath.Join(*imRootDir, "userartifacts"),
	}
	uam, err := orchestrator.NewUserArtifactsManagerImpl(uamOpts)
	if err != nil {
		log.Fatalf("Unable to prepare UserArtifactsManager: %v", err)
	}
	defer os.RemoveAll(uam.WorkDir)
	idmOpts := orchestrator.ImageDirectoriesManagerOpts{
		RootDir: imPaths.ImageDirectoriesDir,
	}
	idm := orchestrator.NewImageDirectoriesManagerImpl(idmOpts)
	debugStaticVars := debug.StaticVariables{}
	debugVarsManager := debug.NewVariablesManager(debugStaticVars)
	imController := orchestrator.Controller{
		Config: orchestrator.Config{
			Paths:                  imPaths,
			AndroidBuildServiceURL: *abURL,
			BuildAPICredentials: orchestrator.BuildAPICredentialsConfig{
				UseGCEMetadata: *buildAPICredsUseGCEMetadata,
			},
		},
		OperationManager:        om,
		WaitOperationDuration:   2 * time.Minute,
		UserArtifactsManager:    uam,
		ImageDirectoriesManager: idm,
		DebugVariablesManager:   debugVarsManager,
	}
	proxy := newOperatorProxy(*operatorPort)

	r := mux.NewRouter()
	imController.AddRoutes(r)
	// Defer to the operator for every route not covered by the orchestrator. That
	// includes web UI and other static files.
	r.PathPrefix("/").Handler(proxy)

	http.Handle("/", r)

	starters := []func() error{
		func() error { return startHttpServer(*address, *httpPort) },
	}
	start(starters)
}
