package saas

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/sts"
	"github.com/future-architect/vuls/config"
	c "github.com/future-architect/vuls/config"
	"github.com/future-architect/vuls/models"
	"github.com/future-architect/vuls/util"
	"golang.org/x/xerrors"
)

// Writer writes results to SaaS
type Writer struct{}

// TempCredential : TempCredential
type TempCredential struct {
	Credential   *sts.Credentials `json:"Credential"`
	S3Bucket     string           `json:"S3Bucket"`
	S3ResultsDir string           `json:"S3ResultsDir"`
}

type payload struct {
	GroupID      int64  `json:"GroupID"`
	Token        string `json:"Token"`
	ScannedBy    string `json:"ScannedBy"`
	ScannedIPv4s string `json:"ScannedIPv4s"`
	ScannedIPv6s string `json:"ScannedIPv6s"`
}

// UploadSaas : UploadSaas
func (w Writer) Write(rs ...models.ScanResult) error {
	if len(rs) == 0 {
		return nil
	}

	ipv4s, ipv6s, err := util.IP()
	if err != nil {
		util.Log.Errorf("Failed to fetch scannedIPs. err: %+v", err)
	}
	hostname, _ := os.Hostname()

	payload := payload{
		GroupID:      c.Conf.Saas.GroupID,
		Token:        c.Conf.Saas.Token,
		ScannedBy:    hostname,
		ScannedIPv4s: strings.Join(ipv4s, ", "),
		ScannedIPv6s: strings.Join(ipv6s, ", "),
	}
	body, err := json.Marshal(payload)
	if err != nil {
		return xerrors.Errorf("Failed to Marshal to JSON: %w", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.Conf.Saas.URL, bytes.NewBuffer(body))
	defer cancel()
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/json")
	client, err := util.GetHTTPClient(config.Conf.HTTPProxy)
	if err != nil {
		return err
	}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if resp.StatusCode != 200 {
		return xerrors.Errorf("Failed to get Credential. Request JSON : %s,", string(body))
	}

	t, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	var tempCredential TempCredential
	if err := json.Unmarshal(t, &tempCredential); err != nil {
		return xerrors.Errorf("Failed to unmarshal saas credential file. err : %s", err)
	}

	sess, err := session.NewSession(&aws.Config{
		Credentials: credentials.NewStaticCredentialsFromCreds(credentials.Value{
			AccessKeyID:     *tempCredential.Credential.AccessKeyId,
			SecretAccessKey: *tempCredential.Credential.SecretAccessKey,
			SessionToken:    *tempCredential.Credential.SessionToken,
		}),
		Region: aws.String("ap-northeast-1"),
	})
	if err != nil {
		return xerrors.Errorf("Failed to new aws session. err: %w", err)
	}

	svc := s3.New(sess)
	for _, r := range rs {
		b, err := json.Marshal(r)
		if err != nil {
			return xerrors.Errorf("Failed to Marshal to JSON: %w", err)
		}
		util.Log.Infof("Uploading...: ServerName: %s, ", r.ServerName)
		s3Key := renameKeyName(r.ServerUUID, r.Container)
		putObjectInput := &s3.PutObjectInput{
			Bucket: aws.String(tempCredential.S3Bucket),
			Key:    aws.String(path.Join(tempCredential.S3ResultsDir, s3Key)),
			Body:   bytes.NewReader(b),
		}
		if _, err := svc.PutObject(putObjectInput); err != nil {
			return xerrors.Errorf("Failed to upload data to %s/%s, err: %w",
				tempCredential.S3Bucket, s3Key, err)
		}
	}
	util.Log.Infof("done")
	return nil
}

func renameKeyName(uuid string, container models.Container) string {
	if len(container.ContainerID) == 0 {
		return fmt.Sprintf("%s.json", uuid)
	}
	return fmt.Sprintf("%s@%s.json", container.UUID, uuid)
}
