package api

import (
	"math"

	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/domain"
)

// StageProgress summarizes expected/processed counts and failure rate for one stage.
type StageProgress struct {
	Expected    int     `json:"expected"`
	Processed   int     `json:"processed"`
	Failed      int     `json:"failed,omitempty"`
	FailureRate float64 `json:"failure_rate,omitempty"`
	PercentDone float64 `json:"percent_done"`
	Status      string  `json:"status"`
}

// IndexingRunResponse is the REST view of pipeline progress for a snapshot.
type IndexingRunResponse struct {
	IndexingRunID   string                   `json:"indexing_run_id"`
	JobID           string                   `json:"job_id"`
	RepoID          string                   `json:"repo_id"`
	SnapshotID      string                   `json:"snapshot_id"`
	CommitSHA       string                   `json:"commit_sha"`
	Ref             string                   `json:"ref"`
	Mode            string                   `json:"mode"`
	SyncStatus      string                   `json:"sync_status"`
	PipelineStatus  string                   `json:"pipeline_status"`
	ExpectedCommits int                      `json:"expected_commits"`
	FailedFiles     int                      `json:"failed_files"`
	Stages          domain.IndexingRunStages `json:"stages"`
	Progress        map[string]StageProgress `json:"progress"`
	Error           string                   `json:"error,omitempty"`
	StartedAt       string                   `json:"started_at"`
	CompletedAt     string                   `json:"completed_at,omitempty"`
}

// IndexingRunFromDomain maps persisted indexing run metadata to an API response.
func IndexingRunFromDomain(run domain.IndexingRun) IndexingRunResponse {
	progress := map[string]StageProgress{
		"code_parse": {
			Expected:    run.ExpectedCodeFiles,
			Processed:   run.ProcessedCodeFiles,
			Failed:      run.FailedFiles,
			FailureRate: roundRate(firstNonZero(run.CodeParseFailureRate, codeFailureRate(run.FailedFiles, run.ExpectedCodeFiles))),
			PercentDone: percentDone(run.ProcessedCodeFiles, run.ExpectedCodeFiles),
			Status:      run.Stages.CodeParse,
		},
		"docs_parse": {
			Expected:    run.ExpectedDocsFiles,
			Processed:   run.ProcessedDocsFiles,
			PercentDone: percentDone(run.ProcessedDocsFiles, run.ExpectedDocsFiles),
			Status:      run.Stages.DocsParse,
		},
		"repo_sync": {
			Expected:    1,
			Processed:   stageProcessed(run.Stages.RepoSync),
			PercentDone: stagePercent(run.Stages.RepoSync),
			Status:      run.Stages.RepoSync,
		},
		"graph_finalize": {
			Expected:    1,
			Processed:   stageProcessed(run.Stages.GraphFinalize),
			PercentDone: stagePercent(run.Stages.GraphFinalize),
			Status:      run.Stages.GraphFinalize,
		},
		"commit_intel": {
			Expected:    run.ExpectedCommits,
			Processed:   stageProcessed(run.Stages.CommitIntel),
			Failed:      run.FailedCommits,
			FailureRate: roundRate(run.CommitIntelFailureRate),
			PercentDone: stagePercent(run.Stages.CommitIntel),
			Status:      run.Stages.CommitIntel,
		},
		"embedding": {
			Expected:    1,
			Processed:   stageProcessed(run.Stages.Embedding),
			Failed:      run.FailedEmbeddings,
			FailureRate: roundRate(run.EmbeddingFailureRate),
			PercentDone: stagePercent(run.Stages.Embedding),
			Status:      run.Stages.Embedding,
		},
	}

	resp := IndexingRunResponse{
		IndexingRunID:   run.IndexingRunID,
		JobID:           run.JobID,
		RepoID:          run.RepoID,
		SnapshotID:      run.SnapshotID,
		CommitSHA:       run.CommitSHA,
		Ref:             run.Ref,
		Mode:            run.Mode,
		SyncStatus:      run.Status,
		PipelineStatus:  derivePipelineStatus(run),
		ExpectedCommits: run.ExpectedCommits,
		FailedFiles:     run.FailedFiles,
		Stages:          run.Stages,
		Progress:        progress,
		Error:           run.Error,
		StartedAt:       run.StartedAt.UTC().Format("2006-01-02T15:04:05Z"),
	}
	if run.CompletedAt != nil {
		resp.CompletedAt = run.CompletedAt.UTC().Format("2006-01-02T15:04:05Z")
	}
	return resp
}

func derivePipelineStatus(run domain.IndexingRun) string {
	for _, status := range []string{
		run.Stages.RepoSync,
		run.Stages.DocsParse,
		run.Stages.CodeParse,
		run.Stages.GraphFinalize,
		run.Stages.CommitIntel,
		run.Stages.Embedding,
	} {
		if status == domain.StageStatusFailed {
			return domain.IndexingRunStatusFailed
		}
	}

	if allRequiredStagesCompleted(run) {
		if run.FailedFiles > 0 || run.FailedCommits > 0 || run.FailedEmbeddings > 0 {
			return domain.IndexingRunStatusDegraded
		}
		return domain.IndexingRunStatusCompleted
	}

	if run.Status == domain.IndexingRunStatusFailed {
		return domain.IndexingRunStatusFailed
	}

	return domain.IndexingRunStatusRunning
}

func allRequiredStagesCompleted(run domain.IndexingRun) bool {
	required := []string{
		run.Stages.RepoSync,
		run.Stages.GraphFinalize,
		run.Stages.Embedding,
	}
	if run.ExpectedCodeFiles > 0 {
		required = append(required, run.Stages.CodeParse)
	}
	if run.ExpectedDocsFiles > 0 {
		required = append(required, run.Stages.DocsParse)
	}
	if run.ExpectedCommits > 0 {
		required = append(required, run.Stages.CommitIntel)
	}

	for _, status := range required {
		if status != domain.StageStatusCompleted {
			return false
		}
	}
	return true
}

func allStagesCompleted(statuses []string) bool {
	for _, status := range statuses {
		if status != domain.StageStatusCompleted {
			return false
		}
	}
	return true
}

func codeFailureRate(failed, expected int) float64 {
	if expected <= 0 || failed <= 0 {
		return 0
	}
	return float64(failed) / float64(expected)
}

func firstNonZero(value, fallback float64) float64 {
	if value != 0 {
		return value
	}
	return fallback
}

func percentDone(processed, expected int) float64 {
	if expected <= 0 {
		if processed > 0 {
			return 100
		}
		return 0
	}
	value := (float64(processed) / float64(expected)) * 100
	if value > 100 {
		return 100
	}
	return math.Round(value*100) / 100
}

func stagePercent(status string) float64 {
	switch status {
	case domain.StageStatusCompleted:
		return 100
	case domain.StageStatusRunning:
		return 50
	default:
		return 0
	}
}

func stageProcessed(status string) int {
	if status == domain.StageStatusCompleted {
		return 1
	}
	return 0
}

func roundRate(value float64) float64 {
	return math.Round(value*10000) / 10000
}
