package aws

import (
	"bytes"
	"context"
	"fmt"
	"strconv"
	"strings"
	"text/template"
	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/athena"
	"github.com/pingcap/log"
	"github.com/tidbcloud/cost-collect/pkg/global"
)

const SQLTemplate = `SELECT "resource_tags_user_tenant","resource_tags_user_project","resource_tags_user_usedby",
    "line_item_blended_cost","line_item_unblended_cost","line_item_usage_amount","pricing_unit",
    "line_item_resource_id",CONCAT("line_item_product_code",':',"product_servicename") AS "service_name","line_item_operation","product_region_code",
	"line_item_usage_start_date","line_item_usage_end_date",
	"resource_tags_user_cluster","resource_tags_user_component","resource_tags_user_environment","resource_tags_user_servicetype"
FROM "{{ .Database }}"."{{ .Table }}"
WHERE "line_item_usage_start_date" >= timestamp '{{ .StartTime }}' AND "line_item_usage_start_date" < timestamp '{{ .EndTime }}'
    AND "line_item_product_code" IN ('AWSELB','AmazonS3')
	AND "resource_tags_user_usedby" LIKE 'customer%'
    AND "resource_tags_user_tenant" = '{{ .TenantID }}'
	{{ if .ProjectID }}AND "resource_tags_user_project" = '{{ .ProjectID }}'{{ end }}
;
`

type AthenaParam struct {
	global.Request
	AthenaSharedParam
}

type AthenaSharedParam struct {
	Database string
	Table    string
}
type AWSBillingClient struct {
	client       *athena.Athena
	outputBucket string // output to s3
	waitTime     int    //sleep for wait reqeust

	sharedParam AthenaSharedParam
}

func NewBillingClient(outputBucket string, waitTime int, sharedParam AthenaSharedParam) (*AWSBillingClient, error) {
	if outputBucket == "" ||
		sharedParam.Database == "" || sharedParam.Table == "" {
		err := fmt.Errorf("missing aws athena parameter")
		return nil, err
	}

	/*
		cred := credentials.NewStaticCredentials(
			os.Getenv("AWS_ACCESS_KEY_ID"),     // Access key
			os.Getenv("AWS_SECRET_ACCESS_KEY"), // secret
			"",
		)
	*/
	conf := aws.Config{
		//Region: aws.String(os.Getenv("AWS_DEFAULT_REGION")),
		//Credentials: cred,
	}
	sess, err := session.NewSession(&conf)
	if err != nil {
		return nil, err
	}
	athenaClient := athena.New(sess)
	client := AWSBillingClient{
		client:       athenaClient,
		outputBucket: outputBucket,
		waitTime:     waitTime,
		sharedParam:  sharedParam,
	}

	return &client, nil
}

func (t AWSBillingClient) query(sql string) ([]*athena.Row, error) {

	resultConf := &athena.ResultConfiguration{
		OutputLocation: aws.String(t.outputBucket),
	}
	params := &athena.StartQueryExecutionInput{
		QueryString:         aws.String(sql),
		ResultConfiguration: resultConf,
	}

	// create exec job
	req, output := t.client.StartQueryExecutionRequest(params)

	err := req.Send()
	if err != nil {
		log.S().Errorf("RequestError: err:%s", err)
		return nil, err
	}

	log.S().Debugf("QuerySQL:%s", sql)
	log.S().Debugf("Query Execution Response ID: %s", *output.QueryExecutionId)

	var MaxResults int64 = 1000

	params2 := &athena.GetQueryResultsInput{
		MaxResults:       &MaxResults,
		QueryExecutionId: output.QueryExecutionId,
	}

	// wait job ready
	rows := []*athena.Row{}
	nextToekn := ""
	for {
		// Too much data requires pagination
		if nextToekn != "" {
			params2 = params2.SetNextToken(nextToekn)
			nextToekn = ""
		} else {
			time.Sleep(time.Second * time.Duration(t.waitTime))
		}

		req2, output2 := t.client.GetQueryResultsRequest(params2)
		err = req2.Send()

		if err != nil {
			if strings.HasPrefix(err.Error(), "InvalidRequestException:") &&
				(strings.Contains(err.Error(), "Current state: QUEUED") || strings.Contains(err.Error(), "Current state: RUNNING")) {

				log.S().Debugf("taskwait:%s,%s", *output.QueryExecutionId, err)
				continue
			}

			log.S().Errorf("Request err:%s", err)

			return nil, err
		}
		rows = append(rows, output2.ResultSet.Rows...)

		if output2.NextToken != nil {
			nextToekn = *output2.NextToken
			log.S().Infof("rows has more page,rows now %d", len(rows))
			continue
		}
		break
	}
	return rows, nil
}

func (t AWSBillingClient) GetProviderCost(ctx context.Context, request global.Request) ([]global.Item, error) {
	rows, err := t.ExecProviderCostQuery(ctx, request)
	if err != nil {
		return nil, err
	}
	schema := []string{}
	for _, v := range rows[0].Data {
		schema = append(schema, *v.VarCharValue)
	}
	items := []global.Item{}
	for _, row := range rows[1:] {

		// load label
		item, err := dataExtract(schema, row.Data)
		if err != nil {
			return nil, err
		}
		items = append(items, *item)
	}
	return items, nil
}

func (t AWSBillingClient) ExecProviderCostQuery(ctx context.Context, request global.Request) ([]*athena.Row, error) {
	args := AthenaParam{
		Request:           request,
		AthenaSharedParam: t.sharedParam,
	}
	sql, err := CreateAthenaSQL(args)
	if err != nil {
		return nil, err
	}

	rows, err := t.query(sql)
	if err != nil {
		return nil, err
	}
	return rows, nil
}

// dataExtract let map
func dataExtract(fileds []string, data []*athena.Datum) (*global.Item, error) {
	item := global.Item{}

	labels := map[string]string{}

	labelPrefix := "resource_tags_user_"

	startTimeKey := "line_item_usage_start_date"
	endTimeKey := "line_item_usage_end_date"
	amountKey := "line_item_usage_amount"

	//blendedCost := "line_item_blended_cost"
	unblendedCost := "line_item_unblended_cost"
	costKey := unblendedCost

	var err error

	for idx, name := range fileds {
		value := *data[idx].VarCharValue
		if strings.HasPrefix(name, labelPrefix) {
			key := strings.TrimPrefix(name, labelPrefix)
			if key != "usedby" && key != "servicetype" {
				key += "_id"
			}
			labels[key] = value
		} else {
			switch name {
			case startTimeKey:
				item.StartTime = strings.TrimSuffix(value, ".000")
			case endTimeKey:
				item.EndTime = strings.TrimSuffix(value, ".000")
			case costKey: // cost
				item.Cost, err = strconv.ParseFloat(value, 64)
				if err != nil {
					return nil, err
				}
			case amountKey: //amount
				item.Amount, err = strconv.ParseFloat(value, 64)
				if err != nil {
					return nil, err
				}
			case "pricing_unit":
				item.Unit = value
			case "service_name":
				item.ServiceName = value
			case "line_item_operation":
				item.Description = value
			case "product_region_code":
				item.Location = value
			}

		}
	}

	err = global.Coverage(labels, &item.Labels)
	if err != nil {
		return nil, err
	}

	// service type
	if strings.HasPrefix(item.ServiceName, "AWSELB") {
		item.ResourceType = global.CloudTypeNetwork
	} else if strings.HasPrefix(item.ServiceName, "AmazonS3") {
		item.ResourceType = global.CloudTypeStorage
	}
	item.Labels.Provider = "aws"
	item.Labels.UsedBy = "customer" //compatibility old tags,it use customer-tidb
	return &item, nil
}

func CreateAthenaSQL(args AthenaParam) (string, error) {
	tpl, err := template.New("athena").Parse(SQLTemplate)
	if err != nil {
		return "", err
	}

	buf := new(bytes.Buffer)
	err = tpl.Execute(buf, args)

	return buf.String(), err
}
