package amzncorp

import (
	"bytes"
	"context"
	"encoding/json"
	"io/ioutil"
	"net/http"
	"time"

	"github.com/aws/aws-sdk-go/aws/credentials"
)

type IsengardCredentials struct {
	MidwayClient *http.Client

	AWSAccountID string
	IAMRoleName  string

	credentials.Expiry
}

var _ credentials.Provider = (*IsengardCredentials)(nil)

func (c *IsengardCredentials) Retrieve() (credentials.Value, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	return c.retrieveContext(ctx)
}

type isengardGetAssumeRoleCredentialsRequest struct {
	AWSAccountID string `json:"AWSAccountID"`
	IAMRoleName  string `json:"IAMRoleName"`
}

type isengardGetAssumeRoleCredentialsResponse struct {
	AssumeRoleResult string `json:"AssumeRoleResult"`
}

type isengardAssumeRoleResult struct {
	SDKResponseMetadata isengardSDKResponseMetadata `json:"sdkResponseMetadata"`
	SDKHttpMetadata     isengardSDKHttpMetadata     `json:"sdkHttpMetadata"`
	Credentials         assumeRoleCredentials       `json:"credentials"`
	AssumedRoleUser     assumeRoleUser              `json:"assumedRoleUser"`
}

type isengardSDKResponseMetadata struct {
	RequestID string `json:"requestId"`
}

type isengardSDKHttpMetadata struct {
	HTTPHeaders    map[string]string `json:"httpHeaders"`
	HTTPStatusCode int               `json:"httpStatusCode"`
}

type assumeRoleCredentials struct {
	AccessKeyID      string `json:"accessKeyId"`
	SecretAccessKey  string `json:"secretAccessKey"`
	SessionToken     string `json:"sessionToken"`
	ExpirationMillis int64  `json:"expiration"`
}

type assumeRoleUser struct {
	AssumedRoleID string `json:"assumedRoleId"`
	ARN           string `json:"arn"`
}

func call(ctx context.Context, client *http.Client, url string, target string, input, output interface{}) error {
	buf, err := json.Marshal(input)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", url, bytes.NewReader(buf))
	if err != nil {
		return err
	}
	req.Header.Set("X-Amz-Target", target)
	req.Header.Set("Content-Encoding", "amz-1.0")
	req.Header.Set("Content-Type", "application/json; charset=UTF-8")
	req = req.WithContext(ctx)

	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	err = json.Unmarshal(body, output)
	if err != nil {
		return err
	}

	return nil
}

func (c *IsengardCredentials) retrieveContext(ctx context.Context) (credentials.Value, error) {
	var v credentials.Value

	input := &isengardGetAssumeRoleCredentialsRequest{
		AWSAccountID: c.AWSAccountID,
		IAMRoleName:  c.IAMRoleName,
	}
	output := new(isengardGetAssumeRoleCredentialsResponse)

	err := call(ctx, c.MidwayClient,
		"https://isengard-service.amazon.com",
		"IsengardService.GetAssumeRoleCredentials",
		input, output)
	if err != nil {
		return v, err
	}

	result := new(isengardAssumeRoleResult)

	err = json.Unmarshal([]byte(output.AssumeRoleResult), result)
	if err != nil {
		return v, err
	}

	v.AccessKeyID = result.Credentials.AccessKeyID
	v.SecretAccessKey = result.Credentials.SecretAccessKey
	v.SessionToken = result.Credentials.SessionToken
	v.ProviderName = "IsengardProvider"

	expTime := time.Unix(result.Credentials.ExpirationMillis/int64(time.Second/time.Millisecond), 0).UTC()
	c.Expiry.SetExpiration(expTime, 10*time.Second)

	return v, nil
}
