// Copyright (C) 2019 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at https://mozilla.org/MPL/2.0/.

package main

import (
	"bufio"
	"crypto/tls"
	"encoding/json"
	"flag"
	"log"
	"os"
	"reflect"

	"github.com/AudriusButkevicius/recli"
	"github.com/flynn-archive/go-shlex"
	"github.com/mattn/go-isatty"
	"github.com/pkg/errors"
	"github.com/syncthing/syncthing/lib/build"
	"github.com/syncthing/syncthing/lib/config"
	"github.com/syncthing/syncthing/lib/events"
	"github.com/syncthing/syncthing/lib/locations"
	"github.com/syncthing/syncthing/lib/protocol"
	"github.com/urfave/cli"
)

func main() {
	// This is somewhat a hack around a chicken and egg problem.
	// We need to set the home directory and potentially other flags to know where the syncthing instance is running
	// in order to get it's config ... which we then use to construct the actual CLI ... at which point it's too late
	// to add flags there...
	homeBaseDir := locations.GetBaseDir(locations.ConfigBaseDir)
	guiCfg := config.GUIConfiguration{}

	flags := flag.NewFlagSet("", flag.ContinueOnError)
	flags.StringVar(&guiCfg.RawAddress, "gui-address", guiCfg.RawAddress, "Override GUI address (e.g. \"http://192.0.2.42:8443\")")
	flags.StringVar(&guiCfg.APIKey, "gui-apikey", guiCfg.APIKey, "Override GUI API key")
	flags.StringVar(&homeBaseDir, "home", homeBaseDir, "Set configuration directory")

	// Implement the same flags at the lower CLI, with the same default values (pre-parse), but do nothing with them.
	// This is so that we could reuse os.Args
	fakeFlags := []cli.Flag{
		cli.StringFlag{
			Name:  "gui-address",
			Value: guiCfg.RawAddress,
			Usage: "Override GUI address (e.g. \"http://192.0.2.42:8443\")",
		},
		cli.StringFlag{
			Name:  "gui-apikey",
			Value: guiCfg.APIKey,
			Usage: "Override GUI API key",
		},
		cli.StringFlag{
			Name:  "home",
			Value: homeBaseDir,
			Usage: "Set configuration directory",
		},
	}

	// Do not print usage of these flags, and ignore errors as this can't understand plenty of things
	flags.Usage = func() {}
	_ = flags.Parse(os.Args[1:])

	// Now if the API key and address is not provided (we are not connecting to a remote instance),
	// try to rip it out of the config.
	if guiCfg.RawAddress == "" && guiCfg.APIKey == "" {
		// Update the base directory
		err := locations.SetBaseDir(locations.ConfigBaseDir, homeBaseDir)
		if err != nil {
			log.Fatal(errors.Wrap(err, "setting home"))
		}

		// Load the certs and get the ID
		cert, err := tls.LoadX509KeyPair(
			locations.Get(locations.CertFile),
			locations.Get(locations.KeyFile),
		)
		if err != nil {
			log.Fatal(errors.Wrap(err, "reading device ID"))
		}

		myID := protocol.NewDeviceID(cert.Certificate[0])

		// Load the config
		cfg, _, err := config.Load(locations.Get(locations.ConfigFile), myID, events.NoopLogger)
		if err != nil {
			log.Fatalln(errors.Wrap(err, "loading config"))
		}

		guiCfg = cfg.GUI()
	} else if guiCfg.Address() == "" || guiCfg.APIKey == "" {
		log.Fatalln("Both -gui-address and -gui-apikey should be specified")
	}

	if guiCfg.Address() == "" {
		log.Fatalln("Could not find GUI Address")
	}

	if guiCfg.APIKey == "" {
		log.Fatalln("Could not find GUI API key")
	}

	client := getClient(guiCfg)

	cfg, err := getConfig(client)
	original := cfg.Copy()
	if err != nil {
		log.Fatalln(errors.Wrap(err, "getting config"))
	}

	// Copy the config and set the default flags
	recliCfg := recli.DefaultConfig
	recliCfg.IDTag.Name = "xml"
	recliCfg.SkipTag.Name = "json"

	commands, err := recli.New(recliCfg).Construct(&cfg)
	if err != nil {
		log.Fatalln(errors.Wrap(err, "config reflect"))
	}

	// Construct the actual CLI
	app := cli.NewApp()
	app.Name = "stcli"
	app.HelpName = app.Name
	app.Author = "The Syncthing Authors"
	app.Usage = "Syncthing command line interface"
	app.Version = build.Version
	app.Flags = fakeFlags
	app.Metadata = map[string]interface{}{
		"client": client,
	}
	app.Commands = []cli.Command{
		{
			Name:        "config",
			HideHelp:    true,
			Usage:       "Configuration modification command group",
			Subcommands: commands,
		},
		showCommand,
		operationCommand,
		errorsCommand,
	}

	tty := isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
	if !tty {
		// Not a TTY, consume from stdin
		scanner := bufio.NewScanner(os.Stdin)
		for scanner.Scan() {
			input, err := shlex.Split(scanner.Text())
			if err != nil {
				log.Fatalln(errors.Wrap(err, "parsing input"))
			}
			if len(input) == 0 {
				continue
			}
			err = app.Run(append(os.Args, input...))
			if err != nil {
				log.Fatalln(err)
			}
		}
		err = scanner.Err()
		if err != nil {
			log.Fatalln(err)
		}
	} else {
		err = app.Run(os.Args)
		if err != nil {
			log.Fatalln(err)
		}
	}

	if !reflect.DeepEqual(cfg, original) {
		body, err := json.MarshalIndent(cfg, "", "  ")
		if err != nil {
			log.Fatalln(err)
		}
		resp, err := client.Post("system/config", string(body))
		if err != nil {
			log.Fatalln(err)
		}
		if resp.StatusCode != 200 {
			body, err := responseToBArray(resp)
			if err != nil {
				log.Fatalln(err)
			}
			log.Fatalln(string(body))
		}
	}
}
