package imports

import (
	"sort"
	"time"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

const (
	// Here is an estimation on each portion of an import.
	// It is based on the elasptime of each phases from a real-world case.
	maxPreProcessedPortion = 50
	maxImportDataPortion   = 25
	maxBuildIndexPortion   = 25
	maxPostProcessPortion  = 10
	maxAnalyzePortion      = 70
)

func updateTimeRecord(timeRecord *cloudv1.TableImportTimeRecord, collectTimeTS int64, tablePhase cloudv1.TableImportPhaseType) {
	if tablePhase.StepGt(cloudv1.TableImportPhasePending) {
		if timeRecord.ImportStartTime == 0 {
			timeRecord.ImportStartTime = collectTimeTS
		}
	}
	if tablePhase.StepGte(cloudv1.TableImportPhaseImported) {
		if timeRecord.ImportFinishTime == 0 {
			timeRecord.ImportFinishTime = collectTimeTS
		}
	}
	if tablePhase.StepGt(cloudv1.TableImportPhaseImported) {
		if timeRecord.PostProcessStartTime == 0 {
			timeRecord.PostProcessStartTime = collectTimeTS
		}
	}
	if tablePhase.StepGte(cloudv1.TableImportPhaseComplete) {
		if timeRecord.PostProcessFinishTime == 0 {
			timeRecord.PostProcessFinishTime = collectTimeTS
		}
	}
	switch tablePhase {
	case cloudv1.TableImportPhasePreProcessing:
		curDuration := timeRecord.PreProcessDuration
		newDuration := collectTimeTS - timeRecord.ImportStartTime
		if newDuration > curDuration {
			timeRecord.PreProcessDuration = newDuration
		}
	case cloudv1.TableImportPhaseImporting:
		curDuration := timeRecord.ImportDuration
		newDuration := collectTimeTS - (timeRecord.ImportStartTime + timeRecord.PreProcessDuration)
		if newDuration > curDuration {
			timeRecord.ImportDuration = newDuration
		}
	case cloudv1.TableImportPhaseIndexBuilding:
		curDuration := timeRecord.BuildIndexDuration
		newDuration := collectTimeTS - (timeRecord.ImportStartTime + timeRecord.PreProcessDuration + timeRecord.ImportDuration)
		if newDuration > curDuration {
			timeRecord.BuildIndexDuration = newDuration
		}
	case cloudv1.TableImportPhasePostProcessing:
		curDuration := timeRecord.PostProcessDuration
		newDuration := collectTimeTS - timeRecord.PostProcessStartTime
		if newDuration > curDuration {
			timeRecord.PostProcessDuration = newDuration
		}
	case cloudv1.TableImportPhaseAnalyzing:
		curDuration := timeRecord.AnalyzeDuration
		newDuration := collectTimeTS - (timeRecord.PostProcessStartTime + timeRecord.PostProcessDuration)
		if newDuration > curDuration {
			timeRecord.AnalyzeDuration = newDuration
		}
	}
}

func calculateTableCompletePercent(tblStatus *cloudv1.CurrentTableStatus) uint32 {
	maxTotal := maxPreProcessedPortion + maxImportDataPortion + maxBuildIndexPortion
	switch tblStatus.Phase {
	case cloudv1.TableImportPhaseUnknown, cloudv1.TableImportPhasePending:
		return 0
	case cloudv1.TableImportPhasePreProcessing:
		var preProcessPortion uint32
		if tblStatus.Size > 0 {
			preProcessPortion = uint32(tblStatus.ProcessedSize * maxPreProcessedPortion / tblStatus.Size)
		} else {
			preProcessPortion = uint32(maxPreProcessedPortion)
		}
		return preProcessPortion * 100 / uint32(maxTotal)
	case cloudv1.TableImportPhaseImporting:
		if tblStatus.TimeRecord.PreProcessDuration == 0 {
			return 0
		}
		var importPortion uint32
		if tblStatus.TimeRecord.PreProcessDuration > 0 {
			importPortion = uint32(tblStatus.TimeRecord.ImportDuration * maxPreProcessedPortion / tblStatus.TimeRecord.PreProcessDuration)
		}
		if importPortion >= maxImportDataPortion {
			importPortion = maxImportDataPortion
		}
		return (maxPreProcessedPortion + importPortion) * 100 / uint32(maxTotal)
	case cloudv1.TableImportPhaseIndexBuilding:
		if tblStatus.TimeRecord.PreProcessDuration == 0 {
			return 0
		}
		var buildIndexPortion uint32
		if tblStatus.TimeRecord.PreProcessDuration > 0 {
			buildIndexPortion = uint32(tblStatus.TimeRecord.BuildIndexDuration * maxPreProcessedPortion / tblStatus.TimeRecord.PreProcessDuration)
		}
		if buildIndexPortion >= maxBuildIndexPortion {
			buildIndexPortion = maxBuildIndexPortion
		}
		return (maxPreProcessedPortion + maxImportDataPortion + buildIndexPortion) * 100 / uint32(maxTotal)
	default:
		return 100
	}
}

func calculateTablePostImportCompletePercent(tblStatus *cloudv1.CurrentTableStatus) uint32 {
	maxTotal := maxPostProcessPortion + maxAnalyzePortion
	switch tblStatus.Phase {
	case cloudv1.TableImportPhasePostProcessing:
		if tblStatus.TimeRecord.PreProcessDuration == 0 {
			return 0
		}
		var postProcessPortion uint32
		if tblStatus.TimeRecord.PreProcessDuration > 0 {
			postProcessPortion = uint32(tblStatus.TimeRecord.PostProcessDuration * maxPreProcessedPortion / tblStatus.TimeRecord.PreProcessDuration)
		}
		if postProcessPortion > maxPostProcessPortion {
			postProcessPortion = maxPostProcessPortion
		}
		return postProcessPortion * 100 / uint32(maxTotal)
	case cloudv1.TableImportPhaseAnalyzing:
		var analyzePortion uint32
		if tblStatus.TimeRecord.PreProcessDuration > 0 {
			analyzePortion = uint32(tblStatus.TimeRecord.AnalyzeDuration * maxPreProcessedPortion / tblStatus.TimeRecord.PreProcessDuration)
		}
		if analyzePortion > maxAnalyzePortion {
			analyzePortion = maxAnalyzePortion
		}
		return (maxPostProcessPortion + analyzePortion) * 100 / uint32(maxTotal)
	case cloudv1.TableImportPhaseComplete, cloudv1.TableImportPhaseFailed:
		return 100
	default:
		return 0
	}
}

func estimateTableImportPhase(overallPhase cloudv1.ImportConditionType, taskPhase cloudv1.ImportTaskPhaseType, tblProgress *TableProgress) cloudv1.TableImportPhaseType {
	if len(tblProgress.Message) > 0 {
		return cloudv1.TableImportPhaseFailed
	}
	switch tblProgress.Status {
	case taskStatusRunning:
		if tblProgress.TotalWritten >= tblProgress.TotalSize {
			return cloudv1.TableImportPhaseImporting
		} else {
			return cloudv1.TableImportPhasePreProcessing
		}
	case taskStatusCompleted:
		switch {
		case taskPhase == cloudv1.ImportTaskPhasePostProcessing:
			return cloudv1.TableImportPhasePostProcessing
		case overallPhase == cloudv1.ImportCompleted:
			return cloudv1.TableImportPhaseComplete
		case overallPhase == cloudv1.ImportFailed:
			return cloudv1.TableImportPhaseFailed
		default:
			return cloudv1.TableImportPhaseImported
		}
	default:
		return cloudv1.TableImportPhasePending
	}
}

func getTableImportPhase(s CheckpointStatus, taskPhase cloudv1.ImportTaskPhaseType, tblProgress *TableProgress) cloudv1.TableImportPhaseType {
	if len(tblProgress.Message) > 0 {
		return cloudv1.TableImportPhaseFailed
	}
	switch s {
	case CheckpointStatusMissing:
		return cloudv1.TableImportPhaseUnknown
	case CheckpointStatusLoaded, CheckpointStatusAllWritten:
		if tblProgress.TotalWritten >= tblProgress.TotalSize {
			return cloudv1.TableImportPhaseImporting
		} else {
			return cloudv1.TableImportPhasePreProcessing
		}
	case CheckpointStatusClosed:
		return cloudv1.TableImportPhaseImporting
	case CheckpointStatusImported:
		return cloudv1.TableImportPhaseIndexBuilding
	case CheckpointStatusIndexImported:
		return cloudv1.TableImportPhaseImported
	case CheckpointStatusAlteredAutoInc:
		switch taskPhase {
		case cloudv1.ImportTaskPhasePostProcessing:
			return cloudv1.TableImportPhasePostProcessing
		default:
			return cloudv1.TableImportPhaseImported
		}
	case CheckpointStatusChecksumSkipped, CheckpointStatusChecksummed:
		return cloudv1.TableImportPhaseAnalyzing
	case CheckpointStatusAnalyzeSkipped, CheckpointStatusAnalyzed:
		return cloudv1.TableImportPhaseComplete
	default:
		return cloudv1.TableImportPhaseUnknown
	}
}

func saveImportStatus(importStatus *cloudv1.ImportStatus, task *TaskProgress, tblCheckpointMap map[string]*TableCheckpoint) {
	collectTime := time.Now().Unix()

	// update phase
	if task.Status == taskStatusRunning {
		importStatus.Phase = cloudv1.ImportRunning
		importStatus.IsTaskCreated = true
	}

	if task.Status == taskStatusCompleted {
		if task.Message == "" {
			importStatus.Phase = cloudv1.ImportCompleted
		} else {
			importStatus.Phase = cloudv1.ImportFailed
		}
	}

	// collect some basic statistics
	var totalSize uint64
	var totalProcessedSize int64
	var completedTables int
	var pendingTables int
	totalTables := len(task.Tables)
	for _, tableInfo := range task.Tables {
		totalSize += uint64(tableInfo.TotalSize)     // sum of total size
		totalProcessedSize += tableInfo.TotalWritten // sum of processed size
		switch tableInfo.Status {
		case taskStatusNotStarted:
			pendingTables++
		case taskStatusCompleted:
			completedTables++
		}
	}

	// get the running phase
	if importStatus.Phase == cloudv1.ImportRunning {
		if totalTables == completedTables {
			importStatus.TaskPhase = cloudv1.ImportTaskPhasePostProcessing
		} else {
			importStatus.TaskPhase = cloudv1.ImportTaskPhaseImporting
		}
	} else {
		importStatus.TaskPhase = cloudv1.ImportTaskPhaseUnknown
	}

	var files uint32
	var weightedPercentSum float64 = 0.0
	var weightedPostImportPercentSum float64 = 0.0
	tblDetailStatusMap := map[string]cloudv1.CurrentTableStatus{}
	for _, tblStatus := range importStatus.AllTableDetails {
		tblDetailStatusMap[tblStatus.Name] = tblStatus
	}
	// construct current table status list and percent
	for name, tableInfo := range task.Tables {
		var (
			current cloudv1.CurrentTableStatus
			ok      bool
		)
		current, ok = tblDetailStatusMap[name]
		if !ok {
			current = cloudv1.CurrentTableStatus{
				Name:             name,
				Phase:            cloudv1.TableImportPhaseUnknown,
				Size:             uint64(tableInfo.TotalSize),
				ProcessedSize:    uint64(tableInfo.TotalWritten),
				CompletedPercent: 0,
				Message:          tableInfo.Message,
			}
		} else {
			current.Size = uint64(tableInfo.TotalSize)
			current.ProcessedSize = uint64(tableInfo.TotalWritten)
			current.Message = tableInfo.Message
		}

		// determine the current table phase
		var curTblPhase cloudv1.TableImportPhaseType
		if tblCkpt, ok := tblCheckpointMap[name]; ok {
			curTblPhase = getTableImportPhase(tblCkpt.Status, importStatus.TaskPhase, tableInfo)
		}
		if curTblPhase == cloudv1.TableImportPhaseUnknown {
			curTblPhase = estimateTableImportPhase(importStatus.Phase, importStatus.TaskPhase, tableInfo)
		}
		if curTblPhase.StepGte(current.Phase) {
			current.Phase = curTblPhase
		}

		// update the time record
		updateTimeRecord(&(current.TimeRecord), collectTime, current.Phase)

		// calculate the percent of a table
		var percent uint32 = calculateTableCompletePercent(&current)
		var postImportPercent uint32 = calculateTablePostImportCompletePercent(&current)
		if percent > current.CompletedPercent {
			current.CompletedPercent = percent
		}
		if postImportPercent > current.PostImportCompletedPercent {
			current.PostImportCompletedPercent = postImportPercent
		}
		tblDetailStatusMap[name] = current
		if totalSize > 0 {
			weightedPercentSum += float64(current.CompletedPercent) * float64(current.Size) / float64(totalSize)
			weightedPostImportPercentSum += float64(current.PostImportCompletedPercent) * float64(current.Size) / float64(totalSize)
		} else {
			weightedPercentSum += float64(current.CompletedPercent) / float64(totalTables)
			weightedPostImportPercentSum += float64(current.PostImportCompletedPercent) / float64(totalTables)
		}
	}
	importStatus.CollectTime = collectTime
	importStatus.TotalTables = uint32(totalTables)
	importStatus.CompletedTables = uint32(completedTables)
	importStatus.PendingTables = uint32(pendingTables)
	importStatus.TotalSize = totalSize
	importStatus.ProcessedDataSize = uint64(totalProcessedSize)
	importStatus.Message = importErrorMessageDigest(task.Message)

	currentTables := []cloudv1.CurrentTableStatus{}
	allTableStatuses := []cloudv1.CurrentTableStatus{}
	for _, tblStatus := range tblDetailStatusMap {
		allTableStatuses = append(allTableStatuses, tblStatus)
		switch tblStatus.Phase {
		case cloudv1.TableImportPhasePreProcessing,
			cloudv1.TableImportPhaseImporting,
			cloudv1.TableImportPhaseIndexBuilding:
			currentTables = append(currentTables, tblStatus)
		}
	}
	sort.Slice(currentTables, func(i, j int) bool { return currentTables[i].Name < currentTables[j].Name })
	sort.Slice(allTableStatuses, func(i, j int) bool { return allTableStatuses[i].Name < allTableStatuses[j].Name })
	importStatus.CurrentTables = currentTables
	importStatus.AllTableDetails = allTableStatuses
	importStatus.CompletedPercent = uint32(weightedPercentSum)
	importStatus.PostImportCompletedPercent = uint32(weightedPostImportPercentSum)
	if importStatus.TotalFiles == nil || *importStatus.TotalFiles < files {
		importStatus.TotalFiles = &files
	}
}
