package migration

import (
	"fmt"
	"os"
	"path/filepath"
	"time"

	"github.com/tidbcloud/observability/vm-migration/internal/jsonio"
	"github.com/tidbcloud/observability/vm-migration/internal/schema"
	"github.com/tidbcloud/observability/vm-migration/internal/status"
)

// Row captures one tenant's progress for presentation.
type Row struct {
	Tenant        string  `json:"tenant"`
	Step          string  `json:"step"`
	Status        string  `json:"status"`
	Attempt       int     `json:"attempt"`
	Duration      float64 `json:"duration"` // seconds
	Imported      int     `json:"imported"`
	Verified      int     `json:"verified"`
	ShardsTotal   int     `json:"shardsTotal"`
	BytesMigrated int64   `json:"bytesMigrated"`
	MigrateRate   float64 `json:"migrateRate"` // bytes/sec
}

// Summary is a structured snapshot of loop progress.
//
// Callers format it for presentation (HTTP endpoint).
type Summary struct {
	StatusPath    string  `json:"statusPath"`
	UpdatedAt     string  `json:"updatedAt"`
	Total         int     `json:"total"`
	Done          int     `json:"done"`
	Running       int     `json:"running"`
	Failed        int     `json:"failed"`
	Pending       int     `json:"pending"`
	Percent       int     `json:"percent"`
	Complete      bool    `json:"complete"`
	TotalDuration float64 `json:"totalDuration"` // seconds, summed across tenants
	TotalMigrated int64   `json:"totalMigrated"` // bytes
	Rows          []Row   `json:"rows"`
}

// ReadLoopSummary loads plan and status from disk and computes a Summary.
//
// loopPath is the path to migration-loop.json (absolute or relative to process
// cwd). Returns an error if plan or status file is missing/unreadable.
func ReadLoopSummary(loopPath string) (*Summary, error) {
	absPlan, err := filepath.Abs(loopPath)
	if err != nil {
		return nil, err
	}
	cwd := filepath.Dir(absPlan)
	statusPath := DeriveLoopStatusPath(absPlan)
	if _, err := os.Stat(statusPath); err != nil {
		return nil, fmt.Errorf("%s: %w", statusPath, err)
	}

	var plan schema.MigrationLoopPlan
	if err := jsonio.ReadFile(absPlan, &plan); err != nil {
		return nil, fmt.Errorf("%s: %w", absPlan, err)
	}
	var doc schema.MigrationLoopStatus
	if err := jsonio.ReadFile(statusPath, &doc); err != nil {
		return nil, fmt.Errorf("%s: %w", statusPath, err)
	}

	planByName := map[string]schema.MigrationLoopTenant{}
	for _, t := range plan.Tenants {
		planByName[t.Name] = t
	}
	now := status.UTCNow()

	counts := map[string]int{
		StatusCompleted: 0,
		StatusRunning:   0,
		StatusFailed:    0,
		StatusPending:   0,
	}
	rows := make([]Row, 0, len(doc.Tenants))
	var totalDuration float64
	var totalMigrated int64
	for _, tenant := range doc.Tenants {
		name := tenant.Name
		if name == "" {
			name = "?"
		}
		tp := planByName[name]
		imported, verified, total, bytesMigrated, shards := readShardCounts(cwd, tp.StatusFile)
		st := deriveTenantStatus(tenant)
		counts[st]++
		dur := tenantDurationSeconds(tenant, now)
		if dur > 0 {
			totalDuration += dur
		}
		if bytesMigrated > 0 {
			totalMigrated += bytesMigrated
		}
		rate := computeMigrateRate(tenant, shards, now)
		step := tenant.CurrentStep
		if step == "" {
			step = "-"
		}
		rows = append(rows, Row{
			Tenant:        name,
			Step:          step,
			Status:        st,
			Attempt:       currentAttempt(tenant),
			Duration:      dur,
			Imported:      imported,
			Verified:      verified,
			ShardsTotal:   total,
			BytesMigrated: bytesMigrated,
			MigrateRate:   rate,
		})
	}

	total := len(doc.Tenants)
	done := counts[StatusCompleted]
	pct := 0
	if total > 0 {
		pct = done * 100 / total
	}
	// Complete is the derived finish flag: every tenant done, none failed.
	complete := done == total && counts[StatusFailed] == 0

	return &Summary{
		StatusPath:    statusPath,
		UpdatedAt:     doc.UpdatedAt,
		Total:         total,
		Done:          done,
		Running:       counts[StatusRunning],
		Failed:        counts[StatusFailed],
		Pending:       counts[StatusPending],
		Percent:       pct,
		Complete:      complete,
		TotalDuration: totalDuration,
		TotalMigrated: totalMigrated,
		Rows:          rows,
	}, nil
}

func deriveTenantStatus(tenant schema.MigrationLoopTenantStatus) string {
	if tenant.Completed {
		return StatusCompleted
	}
	for _, s := range tenant.Steps {
		if s.Status == StatusRunning {
			return StatusRunning
		}
	}
	for _, s := range tenant.Steps {
		if s.Status == StatusFailed {
			return StatusFailed
		}
	}
	return StatusPending
}

func currentAttempt(tenant schema.MigrationLoopTenantStatus) int {
	for _, s := range tenant.Steps {
		if s.Status != StatusCompleted {
			return s.Attempts
		}
	}
	if len(tenant.Steps) == 0 {
		return 0
	}
	return tenant.Steps[len(tenant.Steps)-1].Attempts
}

func tenantDurationSeconds(tenant schema.MigrationLoopTenantStatus, now time.Time) float64 {
	if tenant.StartedAt == nil {
		return 0
	}
	end := tenant.FinishedAt
	if end == nil {
		end = new(now)
	}
	return end.Sub(*tenant.StartedAt).Seconds()
}

func readShardCounts(cwd, statusFile string) (imported, verified, total int, bytes int64, shards []schema.Shard) {
	if statusFile == "" {
		return 0, 0, 0, 0, nil
	}
	path := filepath.Join(cwd, statusFile)
	doc, err := status.LoadDocument(path)
	if err != nil {
		return 0, 0, 0, 0, nil
	}
	return doc.Summary.Imported, doc.Summary.Verified, doc.Summary.Total, doc.Summary.TotalBytes, doc.Shards
}

func computeMigrateRate(tenant schema.MigrationLoopTenantStatus, shards []schema.Shard, now time.Time) float64 {
	var migrateStep *schema.MigrationLoopStepStatus
	for i := range tenant.Steps {
		if tenant.Steps[i].Step == "migrate" {
			migrateStep = &tenant.Steps[i]
			break
		}
	}
	if migrateStep == nil || migrateStep.Status != StatusRunning || migrateStep.StartedAt == nil {
		return 0
	}
	var attemptBytes int64
	for _, s := range shards {
		if s.Phase != schema.PhaseImported || s.FinishedAt == nil {
			continue
		}
		if s.FinishedAt.Before(*migrateStep.StartedAt) {
			continue
		}
		if s.Bytes != nil {
			attemptBytes += *s.Bytes
		}
	}
	if attemptBytes <= 0 {
		return 0
	}
	elapsed := now.Sub(*migrateStep.StartedAt).Seconds()
	if elapsed <= 0 {
		return 0
	}
	return float64(attemptBytes) / elapsed
}
