// Copyright 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.
// 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 iirprovider

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"github.com/aws/amazon-ssm-agent/agent/log"
	"github.com/aws/aws-sdk-go/aws/credentials"
)

// IIRRoleProvider gets identity role credentials from instance metadata service
type IIRRoleProvider struct {
	credentials.Expiry
	// ExpiryWindow will allow the credentials to trigger refreshing prior to
	// the credentials actually expiring. This is beneficial so race conditions
	// with expiring credentials do not cause request to fail unexpectedly
	// due to ExpiredTokenException exceptions.
	//
	// So a ExpiryWindow of 10s would cause calls to IsExpired() to return true
	// 10 seconds before the credentials are actually expired.
	//
	// If ExpiryWindow is 0 or less it will be ignored.
	ExpiryWindow time.Duration
	Config       *appconfig.SsmagentConfig
	Log          log.T
	IMDSClient   IEC2MdsSdkClient
}

// Retrieve returns nil if it successfully retrieved the instance identity role credentials.
// Error is returned if the value were not obtainable, or empty.
func (p *IIRRoleProvider) Retrieve() (credentials.Value, error) {
	resp, err := p.IMDSClient.GetMetadata(iirCredentialsPath)
	if err != nil {
		p.Log.Errorf("failed to retrieve instance identity role. Error: %v", err)
		return EmptyCredentials(), err
	}
	respCreds := Ec2RoleCreds{}

	if err := json.NewDecoder(strings.NewReader(resp)).Decode(&respCreds); err != nil {
		p.Log.Errorf("failed to decode instance identity role credentials. Error: %v", err)
		return EmptyCredentials(), err
	}

	if respCreds.Code != "Success" {
		// If an error code was returned something failed requesting the role.
		return EmptyCredentials(), fmt.Errorf("instance metadata response is invalid")
	}

	// Set the expiration window to be half of the token's lifetime. This allows credential refreshes to survive transient
	// network issues more easily. Expiring at half the lifetime also follows the behavior of other protocols such as DHCP
	// https://tools.ietf.org/html/rfc2131#section-4.4.5. Note that not all of the behavior specified in that RFC is
	// implemented, just the suggestion to start renewals at 50% of token validity.
	p.ExpiryWindow = time.Until(respCreds.Expiration) / 2

	// Set the expiration of our credentials
	p.SetExpiration(respCreds.Expiration, p.ExpiryWindow)

	return credentials.Value{
		AccessKeyID:     respCreds.AccessKeyID,
		SecretAccessKey: respCreds.SecretAccessKey,
		SessionToken:    respCreds.Token,
		ProviderName:    ProviderName,
	}, nil

}

// EmptyCredentials returns empty instance identity role credentials
func EmptyCredentials() credentials.Value {
	return credentials.Value{ProviderName: ProviderName}
}
