// Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may not
// use this file except in compliance with the License. A copy of the
// License is located at
//
// http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file 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 process wraps up the os.Process interface and also provides os-specific process lookup functions
package proc

import (
	"errors"
	"fmt"
	"os/exec"
	"time"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"github.com/aws/amazon-ssm-agent/agent/log"
	"github.com/aws/amazon-ssm-agent/common/identity"
	identity2 "github.com/aws/amazon-ssm-agent/common/identity/identity"
	"github.com/aws/amazon-ssm-agent/common/runtimeconfig"
)

var (
	getAppConfig                         = appconfig.Config
	newAgentIdentity                     = identity2.NewAgentIdentity
	runtimeConfigClientCreator           = runtimeconfig.NewIdentityRuntimeConfigClient
	defaultAgentIdentitySelectorCreator  = identity2.NewDefaultAgentIdentitySelector
	runtimeConfigIdentitySelectorCreator = identity2.NewRuntimeConfigIdentitySelector
)

// OSProcess is an abstracted interface of os.Process
type OSProcess interface {
	//generic ssm visible fields
	Pid() int
	StartTime() time.Time
	//kill the attached child process
	Kill() error
	//wait for the child to finish, if parent dies halfway, the child process is detached and becomes orphan
	//On Unix system, orphan is not be killed by systemd or upstart by default
	//On Windows service controller, stop service does not kill orphan by default
	//TODO confirm MSI Installer does not kill the child process
	Wait() error
}

// impl of OSProcess with os.Process embed
type WorkerProcess struct {
	*exec.Cmd
	startTime time.Time
}

func (p *WorkerProcess) Pid() int {
	return p.Cmd.Process.Pid
}

func (p *WorkerProcess) StartTime() time.Time {
	return p.startTime
}

// TODO use the kill functions provided in executes package
func (p *WorkerProcess) Kill() error {
	return p.Cmd.Process.Kill()
}

func (p *WorkerProcess) Wait() error {
	return p.Cmd.Wait()
}

// start a child process, with the resources attached to its parent
func StartProcess(name string, argv []string) (OSProcess, error) {
	//TODO connect stdin and stdout to avoid seelog error
	cmd := exec.Command(name, argv...)
	prepareProcess(cmd)
	err := cmd.Start()
	p := WorkerProcess{
		cmd,
		time.Now().UTC(),
	}

	return &p, err
}

// TODO figure out why sometimes argv does not contain program name
func parseArgv(argv []string) (channelName string, err error) {
	if len(argv) == 1 {
		if argv[0] == appconfig.DefaultDocumentWorker || argv[0] == appconfig.DefaultSessionWorker {
			return "", errors.New("insufficient argument number")
		}
		return argv[0], nil
	} else if len(argv) == 2 {
		if argv[0] == appconfig.DefaultDocumentWorker || argv[0] == appconfig.DefaultSessionWorker {
			return argv[1], nil
		}
		return argv[0], nil
	} else if len(argv) == 3 {
		if argv[0] == appconfig.DefaultDocumentWorker || argv[0] == appconfig.DefaultSessionWorker {
			return argv[1], nil
		}
	}
	return "", fmt.Errorf("executable argument number mismatch: %v", len(argv))
}

func InitializeWorkerDependencies(log log.T, args []string) (*appconfig.SsmagentConfig, identity.IAgentIdentity, string, error) {
	config, err := getAppConfig(true)
	if err != nil {
		return nil, nil, "", fmt.Errorf("failed to initialize config: %v", err)
	}
	channelName, err := parseArgv(args)
	if err != nil {
		return nil, nil, "", fmt.Errorf("failed to parse args: %v", err)
	}
	var selector identity2.IAgentIdentitySelector
	var agentIdentity identity.IAgentIdentity
	runtimeConfigClientHandler := runtimeConfigClientCreator()
	if ok, err := runtimeConfigClientHandler.ConfigExists(); ok && err == nil {
		log.Info("picking up runtime config identity selector")
		selector = runtimeConfigIdentitySelectorCreator(log)
	} else {
		log.Info("picking up default identity selector")
		selector = defaultAgentIdentitySelectorCreator(log)
	}
	agentIdentity, err = newAgentIdentity(log, &config, selector)
	if err != nil {
		return nil, nil, "", fmt.Errorf("failed to get identity: %v", err)
	}

	return &config, agentIdentity, channelName, nil
}
