package report

import (
	"context"
	"encoding/json"

	"github.com/pingcap/log"
	"go.uber.org/zap"

	"github.com/pingcap-inc/clinic/pkg/api/report"
	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/compute_engine"
	"github.com/pingcap-inc/clinic/pkg/datasource"
	"github.com/pingcap-inc/clinic/pkg/db"
	"github.com/pingcap-inc/clinic/pkg/db/model"
	"github.com/pingcap-inc/clinic/pkg/task_manager/task"
	"github.com/pingcap-inc/clinic/pkg/trafficjobclient"
)

var trafficJobClient = trafficjobclient.NewClient("")

type ReportJob struct {
	h       *db.Report
	running bool
	tjStore *trafficjobclient.TrafficComparisonReportStorage
}

func NewReportJob(h *db.Report, running bool, tjStore *trafficjobclient.TrafficComparisonReportStorage) *ReportJob {
	return &ReportJob{
		h:       h,
		running: running,
		tjStore: tjStore,
	}
}

func (j *ReportJob) Run(kc task.K8sClient) error {
	r, err := model.GetReportByUUID(j.h.UUID)
	if err != nil {
		log.Error("get report failed", zap.Error(err))
		if err := model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusFailed)}); err != nil {
			log.Error("update report failed", zap.Error(err))
		}
		return err
	}
	log.Info("report status",
		zap.Any("status", r.Status),
		zap.String("id", r.UUID),
		zap.String("report_uuid", r.UUID),
		zap.String("cluster_id", r.ClusterID),
		zap.Uint("org_id", r.OrgID),
		zap.Uint("creator_id", r.CreatorID),
		zap.String("report_type", r.ReportType),
		zap.Bool("running_scan", j.running),
	)

	if r.ReportType == "traffic_comparison" {
		if j.running {
			return j.runTrafficComparisonWithRunningStatus(r)
		}
		return j.runTrafficComparison(r)
	}

	var arg report.TouchReportRequest
	err = json.Unmarshal([]byte(r.Arg), &arg)
	if err != nil {
		log.Error("unmarshal report request failed", zap.Error(err))
		if err := model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusFailed)}); err != nil {
			log.Error("update report failed", zap.Error(err))
		}
		return err
	}

	org, err := datasource.GetOrganization(&session.CtxUser{Internal: true, Email: "clinic@pingcap.com"}, j.h.OrgID)
	if err != nil {
		log.Error("get org failed", zap.Error(err))
		if err := model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusFailed)}); err != nil {
			log.Error("update report failed", zap.Error(err))
		}
		return err
	}
	orgType := org.OrgType
	if org.OrgType != "cloud" {
		orgType = "op"
		packages, n, err := model.ListUploadFiles(j.h.OrgID, j.h.ClusterID, 0, 0, int64(arg.BeginTime), int64(arg.EndTime))
		if err != nil {
			log.Error("select packages failed", zap.Error(err))
			if err := model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusFailed)}); err != nil {
				log.Error("update report failed", zap.Error(err))
			}
			return err
		}
		if n <= 0 {
			log.Error("empty packages", zap.Error(err))
			if err := model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusFailed)}); err != nil {
				log.Error("update report failed", zap.Error(err))
			}
			return err
		}
		for _, p := range packages {
			// skip touchreport, waiting next tick
			// workround for package without log
			if // p.RebuildLogStatus != string(db.StatusSuccess) ||
			p.RebuildMetricStatus != string(db.StatusSuccess) {
				return nil
			}
		}
	}
	if r.Status == string(db.StatusPending) {
		err = compute_engine.GlobalCMClient.TouchReport(r, orgType)
		if err != nil {
			log.Error("touch compute-engine failed", zap.Error(err))
			if err := model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusFailed)}); err != nil {
				log.Error("update report failed", zap.Error(err))
			}
			return err
		}
		err = model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusRunning)})
		if err != nil {
			return err
		}
	}
	return nil
}

func (j *ReportJob) runTrafficComparison(r *db.Report) error {
	var args trafficjobclient.Request
	if err := json.Unmarshal([]byte(r.Arg), &args); err != nil {
		log.Error("unmarshal report request failed", zap.Error(err))
		if err := model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusFailed)}); err != nil {
			log.Error("update report failed", zap.Error(err))
		}
		return err
	}
	if r.Status != string(db.StatusPending) {
		return nil
	}
	if err := trafficJobClient.Do(&args); err != nil {
		log.Error("touch traffic job failed",
			zap.Error(err),
			zap.String("url", trafficJobClient.URL()),
			zap.String("report_id", args.Options.ReportID),
			zap.String("cluster_a_tenant_id", args.ClusterA.TenantID),
			zap.String("cluster_a_project_id", args.ClusterA.ProjectID),
			zap.String("cluster_a_cluster_id", args.ClusterA.ClusterID),
			zap.String("cluster_a_physical_cluster_id", args.ClusterA.PhysicalClusterID),
			zap.String("cluster_a_dataplane_id", args.ClusterA.DataPlaneID),
			zap.String("cluster_b_tenant_id", args.ClusterB.TenantID),
			zap.String("cluster_b_project_id", args.ClusterB.ProjectID),
			zap.String("cluster_b_cluster_id", args.ClusterB.ClusterID),
			zap.String("cluster_b_physical_cluster_id", args.ClusterB.PhysicalClusterID),
			zap.String("cluster_b_dataplane_id", args.ClusterB.DataPlaneID),
		)
		if err := model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusFailed)}); err != nil {
			log.Error("update report failed", zap.Error(err))
		}
		return err
	}
	return model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusRunning)})
}

func (j *ReportJob) runTrafficComparisonWithRunningStatus(r *db.Report) error {
	var args trafficjobclient.Request
	if err := json.Unmarshal([]byte(r.Arg), &args); err != nil {
		log.Error("unmarshal report request failed", zap.Error(err))
		if err := model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusFailed)}); err != nil {
			log.Error("update report failed", zap.Error(err))
		}
		return err
	}
	if r.Status != string(db.StatusRunning) {
		return nil
	}
	success, err := j.tjStore.MetricsDiffExists(context.Background(), &args)
	if err != nil {
		log.Error("check metrics diff exists failed", zap.Error(err))
		return err
	}
	if !success {
		return nil
	}
	if err := model.UpdateReport(&db.Report{UUID: r.UUID, Status: string(db.StatusSuccess)}); err != nil {
		log.Error("update report failed", zap.Error(err))
	}
	return nil
}
