package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/franela/goreq"
	"github.com/goji/httpauth"
	"github.com/urfave/cli"
)

func postForm(pathname string, data url.Values) (r JSONResponse, err error) {
	resp, err := http.PostForm(cfg.Client.ServerURL+pathname, data)
	if err != nil {
		return r, err
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return r, err
	}
	err = json.Unmarshal(body, &r)
	if err != nil {
		return r, fmt.Errorf("POST %v %v", strconv.Quote(pathname), string(body))
	}
	return r, nil
}

func actionStartServer(c *cli.Context) error {
	suv, hdlr, err := newSupervisorHandler()
	if err != nil {
		log.Fatal(err)
	}

	if c.Bool("foreground") {
		if err = newDistributed(suv, hdlr); err != nil {
			log.Fatal(err)
		}
	}

	auth := cfg.Server.HttpAuth
	if auth.Enabled {
		hdlr = httpauth.SimpleBasicAuth(auth.User, auth.Password)(hdlr)
	}
	http.Handle("/", hdlr)

	addr := cfg.Server.Addr
	if c.Bool("foreground") {
		suv.AutoStartPrograms()
		log.Printf("server listen on %v", addr)
		log.Fatal(http.ListenAndServe(addr, nil))
	} else {
		if checkServerStatus() == nil {
			fmt.Println("server is already running")
			return nil
		}
		logPath := filepath.Join(defaultGosuvDir, "gosuv.log")
		logFd, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
		if err != nil {
			log.Fatalf("create file %s failed: %v", logPath, err)
		}
		cmd := exec.Command(os.Args[0], "start-server", "-f")
		cmd.Stdout = logFd
		cmd.Stderr = logFd
		err = cmd.Start()
		if err != nil {
			log.Fatal(err)
		}
		select {
		case err = <-GoFunc(cmd.Wait):
			log.Fatalf("server started failed, %v", err)
		case <-time.After(200 * time.Millisecond):
			showAddr := addr
			if strings.HasPrefix(addr, ":") {
				showAddr = "0.0.0.0" + addr
			}
			fmt.Printf("server started, listening on %s\n", showAddr)
		}
	}
	return nil
}

func actionStatus(c *cli.Context) error {
	res, err := goreq.Request{
		Uri: cfg.Client.ServerURL + "/api/programs",
	}.Do()
	if err != nil {
		return err
	}
	var programs = make([]struct {
		Program Program `json:"program"`
		Status  string  `json:"status"`
	}, 0)
	if err := res.Body.FromJsonTo(&programs); err != nil {
		return err
	}
	format := "%-23s\t%-8s\n"
	fmt.Printf(format, "PROGRAM NAME", "STATUS")
	for _, p := range programs {
		fmt.Printf(format, p.Program.Name, p.Status)
	}
	return nil
}

// cmd: <start|stop>
func programOperate(cmd, name string) (err error, success bool) {
	res, err := goreq.Request{
		Method: "POST",
		Uri:    cfg.Client.ServerURL + "/api/programs/" + name + "/" + cmd,
	}.Do()
	if err != nil {
		return
	}
	var v = struct {
		Status int `json:"status"`
	}{}
	if err = res.Body.FromJsonTo(&v); err != nil {
		return
	}
	success = v.Status == 0
	return
}

func actionStart(c *cli.Context) (err error) {
	name := c.Args().First()
	err, success := programOperate("start", name)
	if err != nil {
		return
	}
	if success {
		fmt.Println("Started")
	} else {
		fmt.Println("Start failed")
	}
	return nil
}

func actionStop(c *cli.Context) (err error) {
	name := c.Args().First()
	err, success := programOperate("stop", name)
	if err != nil {
		return
	}
	if !success {
		fmt.Println("Stop failed")
	}
	return nil
}

func actionShutdown(c *cli.Context) error {
	restart := c.Bool("restart")
	if restart {
		log.Fatal("Restart not implemented.")
	}
	ret, err := postForm("/api/shutdown", nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(ret.Value)
	return nil
}

func actionUpdateSelf(c *cli.Context) error {
	return githubUpdate(c.Bool("yes"))
}

func actionEdit(c *cli.Context) error {
	cmd := exec.Command("vim", filepath.Join(os.Getenv("HOME"), ".gosuv/programs.yml"))
	cmd.Stdout = os.Stdout
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	return cmd.Run()
}

func actionVersion(c *cli.Context) error {
	fmt.Printf("gosuv version %s\n", version)
	return nil
}

func actionReload(c *cli.Context) error {
	ret, err := postForm("/api/reload", nil)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(ret.Value)
	return nil
}

func actionConfigTest(c *cli.Context) error {
	if _, _, err := newSupervisorHandler(); err != nil {
		log.Fatal(err)
	}
	log.Println("test is successful")
	return nil
}
