package api

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"os"
	"strings"
	"time"

	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/importer/fetchconf"
	log2 "github.com/pingcap-inc/clinic/pkg/importer/log"

	"github.com/gin-gonic/gin"
	"go.uber.org/zap"
	"gorm.io/gorm"

	"github.com/pingcap-inc/clinic/pkg/config"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
	error2 "github.com/pingcap-inc/clinic/pkg/error"
	"github.com/pingcap-inc/clinic/pkg/upload_file/manager"

	"github.com/pingcap/errors"
	"github.com/pingcap/log"

	_ "github.com/pingcap-inc/clinic/pkg/dashboard/statik"
	_ "github.com/pingcap-inc/clinic/pkg/statik"
)

// InitPortalAPI is used for portal with oauth
//func InitPortalAPI(engine *gin.Engine, cli *es.ESClient, manager *manager.FileStoreageManager, cfg config.SinkConfig, sessionManager *session.SessionManager, reportFetcher *reporter.ReporterFetch) {
//	portalFS, err := fs.NewWithNamespace("portal")
//	if err != nil {
//		log.Fatal("failed to embed static files into your binary", zap.Error(err))
//	}
//
//	dashboardFS, err := fs.NewWithNamespace("dashboard")
//	if err != nil {
//		log.Fatal("failed to embed static files into your binary", zap.Error(err))
//	}
//
//	ug := engine.Group("/diag")
//	ug.Use(sessionManager.MWTryAuth)
//	ug.StaticFS("files", portalFS)
//	ug.StaticFS("dashboard", dashboardFS)
//
//	router := engine.Group("/portal")
//	router.Use(sessionManager.MWTryAuth)
//
//	//router.GET("/files/:uuid/logs/reporter/level", func(context *gin.Context) {
//	//	resp, err := logLevelReporter(context, cli)
//	//	SendMessageWithReturn(context, err, resp)
//	//
//	//})
//
//	router.GET("/files/:uuid/meta", func(context *gin.Context) {
//		resp, err := getFileMeta(context, manager)
//		SendMessageWithReturn(context, err, resp)
//	})
//
//	//router.GET("/files/:uuid/related_meta", func(context *gin.Context) {
//	//	resp, err := getRelatedFiles(context)
//	//	SendMessageWithReturn(context, err, resp)
//	//})
//	//
//	//router.GET("/files/:uuid/slow_queries", func(c *gin.Context) {
//	//	resp, err := listSlowQueries(c, cli)
//	//	c.Header("Access-Control-Allow-Origin", "*")
//	//	SendMessageWithReturn(c, err, resp)
//	//})
//	//
//	//router.GET("/files/:uuid/slow_queries/:queryid", func(c *gin.Context) {
//	//	resp, err := getSlowQueryByID(c, cli)
//	//	c.Header("Access-Control-Allow-Origin", "*")
//	//	SendMessageWithReturn(c, err, resp)
//	//})
//
//	router.GET("/files/oauth_download", func(context *gin.Context) {
//		err := downloadFile(context.Query("uuid"), manager, context)
//		if err != nil {
//			SendErrorMessage(context, err)
//		}
//	})
//
//	router.GET("files/conf_download", func(context *gin.Context) {
//		uuid := context.Query("uuid")
//		if uuid == "" {
//			SendErrorMessage(context, error2.RequestError(errors.New("bad parameter")))
//			return
//		}
//		fileName := fmt.Sprintf("%s%s", uuid, fetchconf.FetchConfFileSubffix)
//		err := downloadFileByFileUUID(uuid, excludeLogAndMetricsFileName, fileName, manager, context)
//		if err != nil {
//			SendErrorMessage(context, err)
//		}
//	})
//
//	router.GET("/files/:uuid/log_symptoms", func(context *gin.Context) {
//		resp, err := getLogSymptoms(context)
//		SendMessageWithReturn(context, err, resp)
//	})
//
//	router.GET("/dashboards", func(context *gin.Context) {
//		dashboards, err := listDashboards(cfg.MetricConfig.DashboardPath)
//		if err != nil {
//			log.Error("read dashboard failed", zap.String("dashboard", cfg.MetricConfig.DashboardPath), zap.Error(err))
//			SendErrorMessage(context, error2.InternalError(errors.New("read dashboard failed")))
//			return
//		}
//
//		SendMessageWithReturn(context, nil, dashboards)
//	})
//
//	router.GET("/logIndices", func(c *gin.Context) {
//		indices, err := listLogConfIndex(c, cfg.LogConfig)
//		SendMessageWithReturn(c, err, indices)
//	})
//	router.GET("/report/:clusterid/:uuid/get_report", func(context *gin.Context) {
//		err := getReport(reportFetcher, context)
//		if err != nil {
//			SendErrorMessage(context, err)
//		}
//	})
//}

//func getUploadFile(uuid string) (*db.UploadFile, *error2.ClinicErr) {
//	if uuid == "" {
//		log.Warn("empty file uuid parameter")
//		return nil, error2.RequestError(errors.New("bad parameter"))
//	}
//
//	file, err := model.QueryUploadFile(uuid)
//	if err != nil {
//		if err == gorm.ErrRecordNotFound {
//			log.Warn("file not found", zap.Error(err))
//			return nil, error2.NotFoundError(errors.New("file not found"))
//		}
//		return nil, error2.InternalError(err)
//	}
//
//	if file == nil {
//		return nil, error2.NotFoundError(errors.New("file not found"))
//	}
//
//	if file.ClusterID == 0 {
//		return nil, error2.ProcessingError(errors.New("file is in processing"))
//	}
//
//	return file, nil
//}

//func getSlowQueryByID(c *gin.Context, cli *es.ESClient) (map[string]interface{}, *error2.ClinicErr) {
//	id := c.Param("queryid")
//	if id == "" {
//		log.Warn("empty query id parameter")
//		return nil, error2.RequestError(errors.New("bad parameter"))
//	}
//
//	file, err := getUploadFile(c.Param("uuid"))
//	if err != nil {
//		return nil, err
//	}
//
//	if file.RebuildLogStatus != string(db.StatusSuccess) {
//		return nil, error2.RequestError(errors.New("the file is processing"))
//	}
//
//	builder := es.NewQueryBuilder(fmt.Sprintf("%d_slowquery", file.ClusterID),
//		es.NewBaseQuery().And(es.NewTermQuery("_id", id)), nil, &es.QueryBuildOption{Size: 1})
//
//	context, cancel := context.WithTimeout(c.Request.Context(), time.Second*30)
//	defer cancel()
//
//	resp, esErr := cli.Do(context, builder)
//	if esErr != nil {
//		log.Error("query es failed", zap.String("id", id), zap.Error(esErr))
//		return nil, error2.InternalError(errors.New("query failed"))
//	}
//
//	if resp == nil || len(resp.Results) == 0 {
//		return nil, nil
//	}
//
//	r := resp.Results[0]
//
//	p, ok := r["plan"].(string)
//	if !ok {
//		log.Warn("plan is not string type", zap.String("id", id), zap.Any("plan", r["plan"]))
//		return r, nil
//	}
//
//	p = strings.TrimSuffix(strings.TrimPrefix(p, "tidb_decode_plan('"), "')")
//
//	s, decodeErr := slow_query.DecodePlan(p)
//	if decodeErr != nil {
//		log.Error("decode plan failed", zap.String("id", id), zap.Error(decodeErr))
//		s = fmt.Sprintf("Raw plan is: %s. Decode plan failed: %v", p, decodeErr.Error())
//	}
//
//	r["plan"] = s
//
//	return r, nil
//}

//func listSlowQueries(c *gin.Context, cli *es.ESClient) ([]map[string]interface{}, *error2.ClinicErr) {
//	var req GetSlowQueryListRequest
//	if err := c.ShouldBindQuery(&req); err != nil {
//		return nil, error2.RequestError(err)
//	}
//
//	file, err := getUploadFile(c.Param("uuid"))
//	if err != nil {
//		return nil, err
//	}
//
//	if file.RebuildLogStatus != string(db.StatusSuccess) {
//		return nil, error2.RequestError(errors.New("the file is processing"))
//	}
//
//	queryBuilder := es.NewBaseQuery()
//
//  //nolint:misspell
//	queryBuilder.And(es.NewTermQuery("packageid", file.FileUUID))
//	if req.BeginTime != 0 && req.EndTime != 0 {
//		queryBuilder.And(es.NewRangeQuery("timestamp", int64(req.BeginTime)*1000, int64(req.EndTime)*1000))
//	}
//
//	opt := &es.QueryBuildOption{}
//
//	if req.Limit <= 0 {
//		req.Limit = 100
//	}
//
//	if req.Limit > 5000 {
//		req.Limit = 5000
//	}
//
//	opt.Size = req.Limit
//
//	opt.Sort = map[string]es.SortType{
//		getSortField(req.OrderBy): func() es.SortType {
//			if req.IsDesc {
//				return es.Desc
//			}
//
//			return es.Asc
//		}(),
//	}
//
//	if req.Text != "" {
//		lowerStr := strings.ToLower(req.Text)
//		arr := strings.Fields(lowerStr)
//
//		for _, v := range arr {
//			v = fmt.Sprintf("*%s*", v)
//			queryBuilder.Should(es.NewWildCardQuery("txn_start_ts.keyword", v)).
//				Should(es.NewWildCardQuery("digest.keyword", v)).
//				Should(es.NewWildCardQuery("prev_stmt.keyword", v)).
//				Should(es.NewWildCardQuery("query.keyword", v))
//		}
//	}
//	context, cancel := context.WithTimeout(c.Request.Context(), time.Second*30)
//	defer cancel()
//
//	resp, queryErr := cli.Do(context, es.NewQueryBuilder(fmt.Sprintf("%d_slowquery", file.ClusterID), queryBuilder, nil, opt))
//	if queryErr != nil {
//		log.Error("query es failed", zap.Error(queryErr))
//		return nil, error2.InternalError(errors.New("query failed"))
//	}
//
//	if resp == nil {
//		return make([]map[string]interface{}, 0), nil
//	}
//
//	return resp.Results, nil
//}

func getSortField(orderBy string) string {
	if orderBy == "" || orderBy == "timestamp" {
		return "timestamp"
	}

	var columns map[string]any
	err := json.NewDecoder(bytes.NewReader([]byte(log2.SlowQueryLogMapping))).Decode(&columns)
	if err != nil {
		log.Error("get columns failed", zap.Error(err))

		return "timestamp"
	}

	fileds := columns["mappings"].(map[string]any)["properties"].(map[string]any)
	fieldType, ok := fileds[strings.ToLower(orderBy)].(map[string]any)
	if !ok {
		return "timestamp"
	}

	t := fieldType["type"].(string)
	if t != "text" {
		return strings.ToLower(orderBy)
	}

	keyword, ok := fieldType["fields"].(map[string]any)
	if !ok {
		return "timestamp"
	}

	if _, ok := keyword["keyword"]; ok {
		return fmt.Sprintf("%s.keyword", strings.ToLower(orderBy))
	}

	return "timestamp"
}

func excludeLogAndMetricsFileName(originalName string) string {
	index := strings.Index(originalName, ".")
	if index < 0 {
		return fmt.Sprintf("exclude-log-metric-%s", originalName)
	}

	return fmt.Sprintf("%s-exclude-log-metric%s", originalName[:index], originalName[index:])
}

/*
func getReport(reportFetcher *reporter.ReporterFetch, context *gin.Context) *error2.ClinicErr {
	fileUUID := context.Param("uuid")
	clusterID := context.Param("clusterid")

	object, on, err := reportFetcher.GetObject(fileUUID, clusterID, context.Request.Header.Get("Range"))
	if err != nil {
		log.Error("get file failed", zap.String("file", fileUUID), zap.Error(err))
		return err
	}

	extraHeaders := map[string]string{
		"ETag":                object.ETag,
		"Content-Disposition": fmt.Sprintf(`attachment; filename="%s"`, on),
		"Content-Range":       object.ContentRange,
		"Accept-Ranges":       object.AcceptRanges,
		"Content-Length":      fmt.Sprintf("%d", object.ContentLen),
	}

	code := http.StatusOK
	if context.Request.Header.Get("Range") != "" && object.ContentRange != "" {
		code = http.StatusPartialContent
		log.Info("download partial content", zap.String("Request Range", context.Request.Header.Get("Range")), zap.String("Response Range", object.ContentRange), zap.Int64("Response Length", object.ContentLen))
	}

	context.DataFromReader(code, object.ContentLen, object.ContentType, object.Body, extraHeaders)
	return error2.InternalError(object.Body.Close())
}
*/

func getClusterIDByUser(context *gin.Context) *session.UserSession {
	user := context.Request.Header.Get(session.CtxUserKey)
	if user == "" {
		return nil
	}

	us, ok := session.UsersSession[user]
	if !ok {
		return nil
	}

	return us
}

//func queryClusterIDs(context *gin.Context) (*PromResponse, *error2.ClinicErr) {
//	us := getClusterIDByUser(context)
//	if us != nil && us.ClusterID != "" && us.ClusterID != "0" {
//		return &PromResponse{
//			Status: "success",
//			Data: []map[string]string{
//				{
//					metrics.ClusterLabel: us.ClusterID,
//				},
//			},
//		}, nil
//	}
//
//	start, err := strconv.ParseInt(context.Query("start"), 10, 64)
//	if err != nil {
//		start = time.Now().Unix() - 3600
//	}
//
//	end, err := strconv.ParseInt(context.Query("end"), 10, 64)
//	if err != nil {
//		end = time.Now().Unix()
//	}
//	ids, err := model.QueryClusterIDs(start, end)
//	if err != nil {
//		return nil, error2.InternalError(errors.New("get clusterid list failed"))
//	}
//
//	if ids == nil || len(ids) == 0 {
//		return &PromResponse{
//			Status: "success",
//			Data:   make([]map[string]string, 0),
//		}, nil
//	}
//
//	results := make([]map[string]string, len(ids))
//	for i, id := range ids {
//		results[i] = map[string]string{
//			metrics.ClusterLabel: fmt.Sprintf("%d", id),
//		}
//	}
//
//	return &PromResponse{
//		Status: "success",
//		Data:   results,
//	}, nil
//}

func getFileMeta(context *gin.Context, manager *manager.FileStoreageManager) (*FileMetaResponse, *error2.ClinicErr) {
	uuid := context.Param("uuid")

	file, err := model.QueryUploadFile(uuid)
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			log.Warn("file not found", zap.Error(err))
			return nil, error2.NotFoundError(errors.New("file not found"))
		}
		return nil, error2.InternalError(err)
	}

	if file == nil {
		return nil, error2.NotFoundError(errors.New("file not found"))
	}

	if file.ClusterID == "" {
		return nil, error2.ProcessingError(errors.New("file is in processing"))
	}
	issues, errx := listJiraIssueByFileUUID(uuid)
	if errx != nil {
		return nil, errx
	}

	confSize := int64(0)
	if file.RebuildConfigurationStatus == string(db.StatusSuccess) {
		resp, _, err := manager.DescribeFileByFileUUID(file.FileUUID, fmt.Sprintf("%s%s", file.FileUUID, fetchconf.FetchConfFileSubffix))
		if err != nil {
			log.Error("describe file failed", zap.String("fileUUID", file.FileUUID), zap.Error(err))
		}

		if resp != nil {
			confSize = resp.ContentLen
		}
	}

	return &FileMetaResponse{
		UUID:            file.FileUUID,
		ClusterID:       file.ClusterID,
		ClusterName:     file.ClusterName,
		BeginTime:       cstTimeZone(file.BeginTime),
		EndTime:         cstTimeZone(file.EndTime),
		JiraIssues:      issues,
		FileName:        getFileName(file.FileOriginalName),
		FileSize:        getFileMSize(file.FileLen),
		RebuildLog:      file.RebuildLogStatus,
		RebuildMetric:   file.RebuildMetricStatus == string(db.StatusSuccess),
		RebuildReport:   file.ReportStatus == string(db.StatusSuccess),
		FetchConfStatus: (file.RebuildConfigurationStatus == string(db.StatusSuccess)) && confSize > 0,
		FetchConfSize:   getFileMSize(confSize),
	}, nil
}

func getFileName(name string) string {
	if strings.HasSuffix(name, ".tar.zst") {
		return strings.TrimSuffix(name, ".tar.zst")
	}

	if strings.HasSuffix(name, ".zst") {
		return strings.TrimSuffix(name, ".zst")
	}

	return name
}

func queryKibanaIndexPattern(cfg config.LogConfig, index, uuid string) string {
	//indexPattern, err := es.SearchKibanaIndexPattern(0, cfg, index)
	//if err != nil {
	//	log.Error("search kibana index pattern failed", zap.String("fileUUID", uuid), zap.Error(err))
	//}
	//
	//return indexPattern
	return ""
}

func cstTimeZone(t *time.Time) *time.Time {
	if t == nil {
		return t
	}

	location, err := time.LoadLocation("Asia/Shanghai")
	if err != nil {
		log.Error("load Asia/Shanghai failed", zap.Error(err))
		return t
	}

	nt := t.In(location)

	return &nt
}

var byteUnits = []string{"B", "KB", "MB", "GB"}

func getFileMSize(sizeInBytes int64) string {
	fileSizeInBytes := float64(sizeInBytes)
	i := 0

	for fileSizeInBytes > 1024 && i < len(byteUnits)-1 {
		fileSizeInBytes = fileSizeInBytes / float64(1024)
		i++
	}

	return fmt.Sprintf("%v%s", fmt.Sprintf("%.1f", fileSizeInBytes), byteUnits[i])
}

//func getRelatedFiles(context *gin.Context) (*RelatedFileResponse, *error2.ClinicErr) {
//	uuid := context.Param("uuid")
//
//	file, err := model.QueryUploadFile(uuid)
//	if err != nil {
//		if err == gorm.ErrRecordNotFound {
//			log.Warn("file not found", zap.Error(err))
//			return nil, error2.NotFoundError(errors.New("file not found"))
//		}
//		return nil, error2.InternalError(err)
//	}
//
//	if file == nil {
//		return nil, error2.NotFoundError(errors.New("file not found"))
//	}
//
//	if file.ClusterID == "" {
//		return nil, error2.ProcessingError(errors.New("file is in processing"))
//	}
//
//	files, err := model.ListFilesByClusterID(file.ClusterID, 1024)
//	if err != nil {
//		return nil, error2.InternalError(err)
//	}
//
//	r := RelatedFileResponse{
//		ClusterID: file.ClusterID,
//	}
//	for _, f := range files {
//		if f.FileUUID == file.FileUUID {
//			continue
//		}
//		issues, errx := listJiraIssueByFileUUID(f.FileUUID)
//		if err != nil {
//			return nil, errx
//		}
//		r.Files = append(r.Files, FileMetaResponse{
//			ClusterID:   fmt.Sprintf("%d", f.ClusterID),
//			ClusterName: f.ClusterName,
//			BeginTime:   cstTimeZone(f.BeginTime),
//			EndTime:     cstTimeZone(f.EndTime),
//			JiraIssues:  issues,
//			UUID:        f.FileUUID,
//		})
//	}
//
//	return &r, nil
//}

func listLogConfIndex(ctx *gin.Context, conf config.LogConfig) ([]LogConfIndex, *error2.ClinicErr) {
	fileUUID := ctx.Query("uuid")

	if fileUUID == "" {
		log.Warn("empty file uuid parameter")
		return nil, error2.RequestError(errors.New("bad parameter"))
	}

	file, err := model.QueryUploadFile(fileUUID)
	if err != nil {
		log.Error("get file by uuid failed", zap.String("uuid", fileUUID), zap.Error(err))
		return nil, error2.InternalError(errors.New("get file failed"))
	}

	return []LogConfIndex{
		{
			Title:        "Log",
			IndexPattern: queryKibanaIndexPattern(conf, file.ClusterID, fileUUID),
		},
		{
			Title:        "SlowQuery",
			IndexPattern: queryKibanaIndexPattern(conf, fmt.Sprintf("%s_slowquery", file.ClusterID), fileUUID),
		},
	}, nil
}

//func logLevelReporter(ctx *gin.Context, cli *es.ESClient) (*LogLevelReporter, *error2.ClinicErr) {
//	fileUUID := ctx.Param("uuid")
//
//	if fileUUID == "" {
//		log.Warn("empty file uuid parameter")
//		return nil, error2.RequestError(errors.New("bad parameter"))
//	}
//
//	file, err := model.QueryUploadFile(fileUUID)
//	if err != nil {
//		log.Error("get file by uuid failed", zap.String("uuid", fileUUID), zap.Error(err))
//		return nil, error2.InternalError(errors.New("get file failed"))
//	}
//
//	if file == nil {
//		return nil, error2.NotFoundError(errors.New("file not found"))
//	}
//
//	if file.DownloadURL == "" || file.ClusterID == 0 {
//		return nil, error2.InternalError(errors.New("the file is processing"))
//	}
//  //nolint:misspell
//	builder := es.NewQueryBuilder(fmt.Sprintf("%d", file.ClusterID),
//		es.NewBaseQuery().And(es.NewTermQuery("packageid", file.FileUUID)).And(es.NewRangeQuery("timestamp", file.BeginTime.UnixNano()/1000000, file.EndTime.UnixNano()/1000000)),
//		es.NewAggQuery("level.keyword"), nil)
//
//	resp, err := cli.Do(context.TODO(), builder)
//	if err != nil {
//		log.Error("query es failed", zap.Error(err))
//
//		return nil, error2.InternalError(errors.New("query log failed"))
//	}
//
//	reporter := &LogLevelReporter{
//		LogLevels: make([]LogLevel, 0),
//	}
//	if resp.AggResponse.Buckets == nil {
//		return reporter, nil
//	}
//	stream.OfMap(resp.AggResponse.Buckets).Filter(func(entry stream.MapEntry) bool {
//		_, ok := allowLogLevel[entry.Key.String()]
//		return ok
//	}).Each(func(entry stream.MapEntry) {
//		reporter.LogLevels = append(reporter.LogLevels, LogLevel{
//			Level: entry.Key.String(),
//			Count: entry.Value.Int(),
//		})
//	})
//
//	return reporter, nil
//}

//nolint:varcheck
var allowLogLevel = map[string]struct{}{
	"trace":       {},
	"debug":       {},
	"info":        {},
	"information": {},
	"warn":        {},
	"warning":     {},
	"error":       {},
	"critical":    {},
	"fatal":       {},
}

// func listDashboards(dashboardDir string) ([]*Dashboard, error) {
// 	f, err := os.Open(dashboardDir)
// 	if err != nil {
// 		log.Error("open dashboard dir failed", zap.String("dashboard", dashboardDir), zap.Error(err))
// 		return nil, err
// 	}
// 	defer f.Close()

// 	dir, err := f.Stat()
// 	if err != nil {
// 		return nil, err
// 	}

// 	if !dir.IsDir() {
// 		return nil, fmt.Errorf("the path is not directory, path=%s", dashboardDir)
// 	}

// 	files, err := f.Readdir(-1)
// 	if err != nil {
// 		return nil, err
// 	}

// 	results := make([]*Dashboard, 0)

// 	stream.OfSlice(files).Map(func(info os.FileInfo) *Dashboard {
// 		dashboard, err := readDashboardFile(dashboardDir, info)
// 		if err != nil {
// 			log.Error("read dashboard file failed", zap.String("file", f.Name()), zap.Error(err))
// 			return nil
// 		}

// 		return dashboard
// 	}).Filter(func(d *Dashboard) bool {
// 		return d != nil
// 	}).CollectEmptySlice(&results)

// 	return results, nil
// }

func readDashboardFile(dir string, f os.FileInfo) (*Dashboard, error) {
	if !strings.HasSuffix(f.Name(), "json") {
		log.Warn("unexpect file, skip it", zap.String("file", f.Name()))
		return nil, nil
	}
	dashbaordFile, err := os.Open(fmt.Sprintf("%s/%s", dir, f.Name()))
	if err != nil {
		return nil, err
	}
	defer func() {
		if err := dashbaordFile.Close(); err != nil {
			log.Error("dashboard file close failed", zap.Error(err))
		}
	}()

	data, err := io.ReadAll(dashbaordFile)
	if err != nil {
		return nil, err
	}

	var dashboard Dashboard
	err = json.Unmarshal(data, &dashboard)
	if err != nil {
		return nil, err
	}

	return &dashboard, nil
}
