// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License;
// you may not use this file except in compliance with the Elastic License.

package cmd

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"strings"

	"github.com/elastic/beats/libbeat/logp"
	"github.com/elastic/beats/x-pack/functionbeat/core"
	"github.com/elastic/beats/x-pack/functionbeat/provider"
)

// Errors generated by the cliHandler.
var (
	errNoFunctionGiven = errors.New("no function given")
)

// cliHandler takes a provider.CLIManager and acts a bridge between user enterred content from the CLI
// and the type managing the function on the provider. It allow to specify multiple functions at
// the command line but will do a single invocation on the CLIManager and will do general validation
// and normalization of the values. It also communicate the status of the operations to the user.
//
// NOTES: Each execution call of the CLIManager are independant, this mean that a fail call will not
// stop other calls to succeed.
//
// TODO(ph) functions could be merged into a single call , but I thought it was premature to do
// it.
type cliHandler struct {
	cli       provider.CLIManager
	log       *logp.Logger
	errOutput io.Writer
	output    io.Writer
}

func newCLIHandler(cli provider.CLIManager, errOutput io.Writer, output io.Writer) *cliHandler {
	return &cliHandler{cli: cli, errOutput: errOutput, output: output, log: logp.NewLogger("cli-handler")}
}

func (c *cliHandler) Deploy(names []string) error {
	c.log.Debugf("Starting deploy for: %s", strings.Join(names, ", "))
	defer c.log.Debug("Deploy execution ended")

	if len(names) == 0 {
		return errNoFunctionGiven
	}

	errCount := 0
	for _, name := range names {
		if err := c.cli.Deploy(name); err != nil {
			fmt.Fprintf(c.errOutput, "Function: %s, could not deploy, error: %s\n", name, err)
			errCount++
			continue
		}
		fmt.Fprintf(c.output, "Function: %s, deploy successful\n", name)
	}

	if errCount > 0 {
		return fmt.Errorf("Fail to deploy %d function(s)", errCount)
	}
	return nil
}

func (c *cliHandler) Update(names []string) error {
	c.log.Debugf("Starting update for: %s", strings.Join(names, ", "))
	defer c.log.Debug("Update execution ended")

	if len(names) == 0 {
		return errNoFunctionGiven
	}

	errCount := 0
	for _, name := range names {
		if err := c.cli.Update(name); err != nil {
			fmt.Fprintf(c.errOutput, "Function: %s, could not update, error: %s\n", name, err)
			errCount++
			continue
		}
		fmt.Fprintf(c.output, "Function: %s, update successful\n", name)
	}

	if errCount > 0 {
		return fmt.Errorf("fail to deploy %d function(s)", errCount)
	}
	return nil
}

func (c *cliHandler) Remove(names []string) error {
	c.log.Debugf("Starting remove for: %s", strings.Join(names, ", "))
	defer c.log.Debug("Remove execution ended")

	if len(names) == 0 {
		return errNoFunctionGiven
	}

	errCount := 0
	for _, name := range names {
		if err := c.cli.Remove(name); err != nil {
			fmt.Fprintf(c.errOutput, "Function: %s, could not remove, error: %s\n", name, err)
			errCount++
			continue
		}
		fmt.Fprintf(c.output, "Function: %s, remove successful\n", name)
	}

	if errCount > 0 {
		return fmt.Errorf("fail to remove %d function(s)", errCount)
	}
	return nil
}

// TODO(ph) check current path and option flag for cobra
func (c *cliHandler) BuildPackage(output string) error {
	content, err := core.MakeZip()
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(output, content, 0644)
	if err != nil {
		return err
	}

	fmt.Fprintf(c.output, "Generated package at: %s\n", output)
	return nil
}
