// Copyright 2017-2017 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 ec2

import (
	"bytes"
	"encoding/json"
	"net/http"

	"github.com/aws/aws-xray-sdk-go/v2/internal/logger"
	"github.com/aws/aws-xray-sdk-go/v2/internal/plugins"
)

// Origin is the type of AWS resource that runs your application.
const Origin = "AWS::EC2::Instance"

type metadata struct {
	AvailabilityZone string
	ImageID          string
	InstanceID       string
	InstanceType     string
}

// Init activates EC2Plugin at runtime.
func Init() {
	if plugins.InstancePluginMetadata != nil && plugins.InstancePluginMetadata.EC2Metadata == nil {
		addPluginMetadata(plugins.InstancePluginMetadata)
	}
}

func addPluginMetadata(pluginmd *plugins.PluginMetadata) {
	var instanceData metadata
	imdsURL := "http://169.254.169.254/latest/"

	client := &http.Client{
		Transport: http.DefaultTransport,
	}

	token, err := getToken(imdsURL, client)
	if err != nil {
		logger.Debugf("Unable to fetch EC2 instance metadata token fallback to IMDS V1: %v", err)
	}

	resp, err := getMetadata(imdsURL, client, token)
	if err != nil {
		logger.Errorf("Unable to read EC2 instance metadata: %v", err)
		return
	}

	buf := new(bytes.Buffer)
	if _, err := buf.ReadFrom(resp.Body); err != nil {
		logger.Errorf("Error while reading data from response buffer: %v", err)
		return
	}
	metadata := buf.String()

	if err := json.Unmarshal([]byte(metadata), &instanceData); err != nil {
		logger.Errorf("Error while unmarshal operation: %v", err)
		return
	}

	pluginmd.EC2Metadata = &plugins.EC2Metadata{InstanceID: instanceData.InstanceID, AvailabilityZone: instanceData.AvailabilityZone}
	pluginmd.Origin = Origin
}

// getToken fetches token to fetch EC2 metadata
func getToken(imdsURL string, client *http.Client) (string, error) {
	ttlHeader := "X-aws-ec2-metadata-token-ttl-seconds"
	defaultTTL := "60"
	tokenURL := imdsURL + "api/token"

	req, _ := http.NewRequest("PUT", tokenURL, nil)
	req.Header.Add(ttlHeader, defaultTTL)
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}

	buf := new(bytes.Buffer)
	if _, err := buf.ReadFrom(resp.Body); err != nil {
		logger.Errorf("Error while reading data from response buffer: %v", err)
		return "", err
	}
	token := buf.String()

	return token, err
}

// getMetadata fetches instance metadata
func getMetadata(imdsURL string, client *http.Client, token string) (*http.Response, error) {
	var metadataHeader string
	metadataURL := imdsURL + "dynamic/instance-identity/document"

	req, _ := http.NewRequest("GET", metadataURL, nil)
	if token != "" {
		metadataHeader = "X-aws-ec2-metadata-token"
		req.Header.Add(metadataHeader, token)
	}

	return client.Do(req)
}

// Metdata represents IMDS response.
//
// Deprecated: Metdata exists only for backward compatibility.
type Metdata struct {
	AvailabilityZone string
	ImageID          string
	InstanceID       string
	InstanceType     string
}
