package gcp

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

	"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 = `gcp-digital-bi.gcp_billing.gcp_billing_export_v1_010870_592F9D_20D797*`
	SQLTemplate         = `SELECT
	region,sum(amount) as amount,unit,description,service_name,sum(cost) as 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 }}
GROUP BY
	region,unit,description,service_name,
	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
;`
	SQLTemplateV2 = `SELECT
	region,sum(amount) as amount,unit,description,service_name,sum(cost) as 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 cost > 0
	AND (
	    (
			(sku.description LIKE "Network Internet %" OR sku.description LIKE "Network Inter Region %" 
			OR sku.description LIKE "Network Inter Zone %" OR sku.description LIKE "Network Load Balancing:%") 
		    AND service.description = "Compute Engine"
		) 
	    OR (service.description = "Cloud Storage")
   )
)
WHERE
    DATE(_PARTITIONTIME) >= DATE("{{ .StartTime }}") 
	AND DATE(_PARTITIONTIME) <= DATE("{{ .EndTime }}")
	AND user_label_usedby LIKE "customer%"
    AND user_label_component_id != "extra-tidb"
    AND user_label_tenant_id = "{{ .TenantID }}"
	{{ if .ProjectID }}AND user_label_project_id = "{{ .ProjectID }}"{{ end }}
GROUP BY
	region,unit,description,service_name,
	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
;`
)

const (
	DataVersionV1 = "v1"
	DataVersionV2 = "v2"
)

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, request)
		if err != nil {
			return nil, err
		}
		items = append(items, *item)
	}

	return items, nil
}

func dataExtract(row map[string]bigquery.Value, request global.Request) (*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
	item.StartTime = request.StartTime
	item.EndTime = request.EndTime

	item.DataVersion = DataVersionV1
	if request.IsDataVersionV2() {
		item.DataVersion = DataVersionV2
	}
	if request.IsDataVersionV2() {
		// classify for biz flag
		if item.ServiceName == "Compute Engine" && strings.HasPrefix(item.Description, "Network") {
			item.ResourceType = global.CloudTypeNetwork

			// for resource subtype
			if strings.HasPrefix(item.Description, "Network Inter Zone") {
				// cross az
				item.ResourceSubtype = global.CloudNetworkSubTypeCrossZone
			} else if strings.HasPrefix(item.Description, "Network Inter Region") {
				// cross region
				item.ResourceSubtype = global.CloudNetworkSubTypeCrossRegion
			} else if strings.HasPrefix(item.Description, "Network Internet") {
				// internet
				item.ResourceSubtype = global.CloudNetworkSubTypeInternet
			} else if strings.HasPrefix(item.Description, "Network Load Balancing:") {
				// loadbalance process
				item.ResourceSubtype = global.CloudNetworkSubTypeLoadbalance
			}

		} else if item.ServiceName == "Cloud Storage" {
			item.ResourceType = global.CloudTypeStorage
		}
	} else {
		// 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) {
	sql := SQLTemplate
	if args.Request.IsDataVersionV2() {
		sql = SQLTemplateV2
	}

	tpl, err := template.New("bigquery").Parse(sql)
	if err != nil {
		return "", err
	}

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

	return buf.String(), err
}
