/*
 * Copyright 2019 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 httpresource provides methods to download resources over HTTP(s)
package httpresource

import (
	"crypto/rand"
	"fmt"
	"math"
	"math/big"
	"net/http"
	"net/url"
	"os"
	"path/filepath"

	"github.com/aws/amazon-ssm-agent/agent/appconfig"
	"github.com/aws/amazon-ssm-agent/agent/context"
	"github.com/aws/amazon-ssm-agent/agent/fileutil/filemanager"
	"github.com/aws/amazon-ssm-agent/agent/jsonutil"
	"github.com/aws/amazon-ssm-agent/agent/plugins/downloadcontent/httpresource/handler"
	"github.com/aws/amazon-ssm-agent/agent/plugins/downloadcontent/remoteresource"
	"github.com/aws/amazon-ssm-agent/agent/plugins/downloadcontent/types"
	"github.com/aws/amazon-ssm-agent/agent/ssm/ssmparameterresolver"
)

// HTTPResource represents an HTTP(s) resource
type HTTPResource struct {
	context context.T
	Handler handler.IHTTPHandler
}

// HTTPInfo defines the accepted SourceInfo attributes and their json definition
type HTTPInfo struct {
	URL                   types.TrimmedString `json:"url"`
	AuthMethod            types.TrimmedString `json:"authMethod"`
	Username              types.TrimmedString `json:"username"`
	Password              types.TrimmedString `json:"password"`
	AllowInsecureDownload bool                `json:"allowInsecureDownload"`
}

// NewHTTPResource creates a new HTTP resource
func NewHTTPResource(context context.T, info string, bridge ssmparameterresolver.ISsmParameterResolverBridge) (resource *HTTPResource, err error) {
	var httpInfo HTTPInfo
	if httpInfo, err = parseSourceInfo(info); err != nil {
		return nil, err
	}

	parsedUrl, err := url.Parse(httpInfo.URL.Val())
	if err != nil {
		return nil, fmt.Errorf("Invalid URL format: %s", err.Error())
	}

	httpClient := http.Client{}
	httpClient.CloseIdleConnections()

	return &HTTPResource{
		context: context,
		Handler: handler.NewHTTPHandler(httpClient, *parsedUrl, httpInfo.AllowInsecureDownload, handler.HTTPAuthConfig{
			AuthMethod: httpInfo.AuthMethod,
			Username:   httpInfo.Username,
			Password:   httpInfo.Password,
		}, bridge),
	}, nil
}

// DownloadRemoteResource downloads a HTTP resource into a specific download path
func (resource *HTTPResource) DownloadRemoteResource(fileSystem filemanager.FileSystem, downloadPath string) (err error, result *remoteresource.DownloadResult) {
	log := resource.context.Log()
	randInt, randErr := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
	if randErr != nil {
		return fmt.Errorf("error in generating a random int: %w", randErr), nil
	}
	downloadPath = resource.adjustDownloadPath(downloadPath, fmt.Sprintf("%d", randInt), fileSystem)

	err = fileSystem.MakeDirs(filepath.Dir(downloadPath))
	if err != nil {
		return fmt.Errorf("Cannot create download path %s: %v", filepath.Dir(downloadPath), err.Error()), nil
	}

	log.Debug("Destination path to download - ", downloadPath)

	downloadedFilepath, err := resource.Handler.Download(log, fileSystem, downloadPath)
	if err != nil {
		return err, nil
	}

	return nil, &remoteresource.DownloadResult{
		Files: []string{downloadedFilepath},
	}
}

// ValidateLocationInfo validates attribute values of an HTTP resource
func (resource *HTTPResource) ValidateLocationInfo() (isValid bool, err error) {
	return resource.Handler.Validate()
}

// parseSourceInfo unmarshalls the provided SourceInfo input
func parseSourceInfo(sourceInfo string) (httpInfo HTTPInfo, err error) {
	if err = jsonutil.Unmarshal(sourceInfo, &httpInfo); err != nil {
		return httpInfo, fmt.Errorf("SourceInfo could not be unmarshalled for source type HTTP: %s", err.Error())
	}

	return httpInfo, nil
}

// adjustDownloadPath sets the download path of the resource based on the user input
func (resource *HTTPResource) adjustDownloadPath(destPath, fileSuffix string, fileSystem filemanager.FileSystem) string {
	if destPath == "" {
		destPath = appconfig.DownloadRoot
	}

	// Mimicking S3 download functionality, which treats the destination path differently based on the following conditions
	if (fileSystem.Exists(destPath) && fileSystem.IsDirectory(destPath)) || os.IsPathSeparator(destPath[len(destPath)-1]) {
		destPath = filepath.Join(destPath, fmt.Sprintf("%s%s", "download", fileSuffix))
	}

	return destPath
}
