package gcp

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

	"cloud.google.com/go/bigquery"
	gcpauth "github.com/pingcap/dbaas/pkg/infra/provider/gcp/auth"
	"github.com/pingcap/log"
	"github.com/tidbcloud/cost-collect/pkg/global"
	"google.golang.org/api/iterator"
	"google.golang.org/api/option"
)

const (
	TIME_TEMPLATE       = "2006-01-02 15:04:05"
	BIGQUERY_TABLE_NAME = `pingcap-gardener.dbaas_billing.gcp_billing_export_v1_010870_592F9D_20D797`
	SQLTemplate         = `SELECT
    usage_start_time,usage_end_time,
	region,amount,unit,description,service_name,cost,
	user_label_tenant_id,user_label_project_id,user_label_cluster_id,user_label_environment_id,user_label_component_id,
	user_label_usedby,user_label_servicetype
FROM
    (
SELECT
    (select value from UNNEST(labels) where key='tenant' limit 1) as user_label_tenant_id,
	(select value from UNNEST(labels) where key='project' limit 1) as user_label_project_id,
	(select value from UNNEST(labels) where key='cluster' limit 1) as user_label_cluster_id,
	(select value from UNNEST(labels) where key='environment' limit 1) as user_label_environment_id,
	(select value from UNNEST(labels) where key='component' limit 1) as user_label_component_id,
	(select value from UNNEST(labels) where key='usedby' limit 1) as user_label_usedby,
	(select value from UNNEST(labels) where key='servicetype' limit 1) as user_label_servicetype,
	usage_start_time,usage_end_time,_PARTITIONTIME,
	location.region AS region,sku.description AS description,service.description AS service_name,
	usage.pricing_unit AS unit,usage.amount_in_pricing_units AS amount,cost
FROM
     {{ .Table }}
WHERE
    usage_start_time >= "{{ .StartTime }}" AND usage_start_time < "{{ .EndTime }}"
	AND project.id = "{{ .BillProject }}"
	AND (
	    ((sku.description LIKE "Network Internet %" OR sku.description LIKE "Network Inter Region %") AND service.description = "Compute Engine") 
	    OR (service.description = "Cloud Storage")
   )
)
WHERE
    DATE(_PARTITIONTIME) >= DATE("{{ .StartTime }}") 
	AND DATE(_PARTITIONTIME) <= DATE("{{ .EndTime }}")
    AND user_label_tenant_id = "{{ .TenantID }}"
	{{ if .ProjectID }}AND user_label_project_id = "{{ .ProjectID }}"{{ end }}
;`
)

type BigQueryParam struct {
	global.Request

	BigQuerySharedParam
}

type BigQuerySharedParam struct {
	Table       string
	BillProject string // A project that generates bills
}

type BigQueryRows []map[string]bigquery.Value

type GCPBillingClient struct {
	client      *bigquery.Client
	location    string
	sharedParam BigQuerySharedParam
}

// NewBillingClient new a bigquery client
func NewBillingClient(providerProjectID string, location string, sharedParam BigQuerySharedParam) (*GCPBillingClient, error) {
	if providerProjectID == "" || location == "" ||
		sharedParam.Table == "" || sharedParam.BillProject == "" {
		err := fmt.Errorf("gcp config error")
		return nil, err
	}
	ctx := context.Background()
	var opts []option.ClientOption
	if os.Getenv("GOOGLE_APPLICATION_CREDENTIALS") == "" {
		// init opts for kubernetes iam
		var err error
		err = gcpauth.InitWebIdentityEnv()
		if err != nil {
			return nil, err
		}
		opts, err = gcpauth.DefaultClientOptions()
		if err != nil {
			return nil, err
		}
	}
	client, err := bigquery.NewClient(ctx, providerProjectID, opts...)
	if err != nil {
		return nil, fmt.Errorf("bigquery.NewClient: %v", err)
	}
	c := GCPBillingClient{
		client:      client,
		location:    location,
		sharedParam: sharedParam,
	}
	return &c, nil
}

func (t GCPBillingClient) query(ctx context.Context, sql string) (BigQueryRows, error) {
	q := t.client.Query(sql)
	q.Location = t.location

	job, err := q.Run(ctx)
	if err != nil {
		return nil, err
	}
	// wait job
	status, err := job.Wait(ctx)
	if err != nil {
		return nil, err
	}
	// check status
	if err := status.Err(); err != nil {
		return nil, err
	}

	var rows BigQueryRows

	it, err := job.Read(ctx)
	if err != nil {
		return nil, err
	}
	for {
		var row map[string]bigquery.Value
		err2 := it.Next(&row)
		if err2 == iterator.Done {
			break
		}
		if err2 != nil {
			return nil, err2
		}
		rows = append(rows, row)
	}
	return rows, nil
}
func (t GCPBillingClient) ExecProviderCostQuery(ctx context.Context, request global.Request) (BigQueryRows, error) {
	args := BigQueryParam{Request: request, BigQuerySharedParam: t.sharedParam}
	sql, err := CreateBigquerySQL(args)
	if err != nil {
		return nil, err
	}

	rows, err := t.query(ctx, sql)
	if err != nil {
		log.S().Errorf("sql:%s", sql)
		return nil, err
	}
	return rows, err
}

func (t GCPBillingClient) GetProviderCost(ctx context.Context, request global.Request) ([]global.Item, error) {
	rawRows, err := t.ExecProviderCostQuery(ctx, request)
	if err != nil {
		return nil, err
	}
	items := []global.Item{}
	for _, row := range rawRows {
		item, err := dataExtract(row)
		if err != nil {
			return nil, err
		}
		items = append(items, *item)
	}

	return items, nil
}

func dataExtract(row map[string]bigquery.Value) (*global.Item, error) {
	item := global.Item{}
	labelsMap := extractLabels(row)
	err := global.Coverage(labelsMap, &item.Labels)
	if err != nil {
		return nil, err
	}
	err = global.Coverage(row, &item)
	if err != nil {
		return nil, err
	}

	// set time
	startTime := time.Time{}
	err = global.Coverage(row["usage_start_time"], &startTime)
	if err != nil {
		return nil, err
	}
	item.StartTime = startTime.Format(TIME_TEMPLATE)

	endTime := time.Time{}
	err = global.Coverage(row["usage_end_time"], &endTime)
	if err != nil {
		return nil, err
	}
	item.EndTime = endTime.Format(TIME_TEMPLATE)

	// for biz flag
	if item.ServiceName == "Compute Engine" && strings.HasPrefix(item.Description, "Network") {
		item.ResourceType = global.CloudTypeNetwork
	} else if item.ServiceName == "Cloud Storage" {
		item.ResourceType = global.CloudTypeStorage
	}
	item.Labels.Provider = "gcp"
	item.Labels.UsedBy = "customer" //compatibility old tags,it use customer-tidb
	return &item, nil
}

func extractLabels(row map[string]bigquery.Value) map[string]bigquery.Value {
	kv := map[string]bigquery.Value{}
	labelPrefix := "user_label_"
	for k, v := range row {
		if strings.HasPrefix(k, labelPrefix) {
			kv[strings.TrimPrefix(k, labelPrefix)] = v
		}
	}
	return kv
}

func CreateBigquerySQL(args BigQueryParam) (string, error) {
	tpl, err := template.New("bigquery").Parse(SQLTemplate)
	if err != nil {
		return "", err
	}

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

	return buf.String(), err
}
