//
// Copyright (c) 2021 Intel Corporation
//
// 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
//
// http://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 secretstore

import (
	"context"
	"fmt"
	"os/exec"
	"strings"
	"syscall"

	"github.com/edgexfoundry/edgex-go/internal/security/secretstore/config"

	"github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger"
)

const OneShotProvider = "oneshot"

type TokenProvider struct {
	loggingClient logger.LoggingClient
	ctx           context.Context
	execRunner    ExecRunner
	initialized   bool
	secretStore   config.SecretStoreInfo
	resolvedPath  string
}

// NewTokenProvider creates a new TokenProvider
func NewTokenProvider(ctx context.Context, lc logger.LoggingClient, execRunner ExecRunner) *TokenProvider {
	return &TokenProvider{
		loggingClient: lc,
		ctx:           ctx,
		execRunner:    execRunner,
	}
}

// SetConfiguration parses token provider configuration and resolves paths specified therein
func (p *TokenProvider) SetConfiguration(secretStore config.SecretStoreInfo) error {
	var err error
	p.secretStore = secretStore
	if p.secretStore.TokenProviderType != OneShotProvider {
		err = fmt.Errorf("%s is not a supported TokenProviderType", p.secretStore.TokenProviderType)
		return err
	}
	resolvedPath, err := p.execRunner.LookPath(p.secretStore.TokenProvider)
	if err != nil {
		err = fmt.Errorf("Failed to locate %s on PATH: %s", p.secretStore.TokenProvider, err.Error())
		return err
	}
	p.initialized = true
	p.resolvedPath = resolvedPath
	return nil
}

// Launch spawns the token provider function
func (p *TokenProvider) Launch() error {
	if !p.initialized {
		err := fmt.Errorf("TokenProvider object not initialized; call SetConfiguration() first")
		return err
	}

	p.loggingClient.Infof(
		"Launching token provider %s with arguments %s",
		p.resolvedPath,
		strings.Join(p.secretStore.TokenProviderArgs, " "))

	cmd := p.execRunner.CommandContext(p.ctx, p.resolvedPath, p.secretStore.TokenProviderArgs...)
	if err := cmd.Start(); err != nil {
		// For example, this might occur if a shared library was missing
		err = fmt.Errorf("%s failed to launch: %s", p.resolvedPath, err.Error())
		return err
	}

	err := cmd.Wait()
	if exitError, ok := err.(*exec.ExitError); ok {
		waitStatus := exitError.Sys().(syscall.WaitStatus)
		err = fmt.Errorf("%s terminated with non-zero exit code %d", p.resolvedPath, waitStatus.ExitStatus())
		return err
	}
	if err != nil {
		err = fmt.Errorf("%s failed with unexpected error: %s", p.resolvedPath, err.Error())
		return err
	}

	p.loggingClient.Info("token provider exited successfully")
	return nil
}