package migration

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

	"github.com/stretchr/testify/assert"

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

func TestRead(t *testing.T) {
	t.Run("EmptyPlan", func(t *testing.T) {
		dir := t.TempDir()
		loopPath := filepath.Join(dir, "migration-loop.json")
		assert.NoError(t, os.WriteFile(loopPath, []byte(`{"tenants":[]}`), 0o644))
		_, err := ReadLoopSummary(loopPath)
		assert.Error(t, err)
	})

	t.Run("Smoke", func(t *testing.T) {
		dir := t.TempDir()
		loopPath := filepath.Join(dir, "migration-loop.json")
		assert.NoError(t, os.WriteFile(loopPath, []byte(`{"tenants":[{"name":"tenants-1","tenantsFile":"tenants-1.json","statusFile":"tenants-1-status.json","logFile":"tenants-1.log","cmds":[{"step":"migrate"}]}]}`), 0o644))
		statusPath := filepath.Join(dir, "migration-loop-status.json")
		assert.NoError(t, os.WriteFile(statusPath, []byte(`{"updatedAt":"2026-01-01T00:00:00Z","tenants":[{"name":"tenants-1","completed":true,"currentStep":"done","steps":[{"step":"migrate","status":"completed","attempts":1}]}]}`), 0o644))

		s, err := ReadLoopSummary(loopPath)
		assert.NoError(t, err)
		assert.Equal(t, 1, s.Total)
		assert.Equal(t, 1, s.Done)
		assert.True(t, s.Complete)
		assert.Len(t, s.Rows, 1)
		assert.Equal(t, "tenants-1", s.Rows[0].Tenant)
	})

	t.Run("IncompleteWhenAnyTenantNotDone", func(t *testing.T) {
		dir := t.TempDir()
		loopPath := filepath.Join(dir, "migration-loop.json")
		assert.NoError(t, os.WriteFile(loopPath, []byte(`{"tenants":[{"name":"tenants-1","cmds":[{"step":"migrate"}]},{"name":"tenants-2","cmds":[{"step":"migrate"}]}]}`), 0o644))
		statusPath := filepath.Join(dir, "migration-loop-status.json")
		assert.NoError(t, os.WriteFile(statusPath, []byte(`{"updatedAt":"2026-01-01T00:00:00Z","tenants":[{"name":"tenants-1","completed":true,"currentStep":"done","steps":[{"step":"migrate","status":"completed"}]},{"name":"tenants-2","steps":[{"step":"migrate","status":"pending"}]}]}`), 0o644))

		s, err := ReadLoopSummary(loopPath)
		assert.NoError(t, err)
		assert.Equal(t, 2, s.Total)
		assert.Equal(t, 1, s.Done)
		assert.False(t, s.Complete)
	})

	t.Run("IncompleteWhenFailed", func(t *testing.T) {
		dir := t.TempDir()
		loopPath := filepath.Join(dir, "migration-loop.json")
		assert.NoError(t, os.WriteFile(loopPath, []byte(`{"tenants":[{"name":"tenants-1","cmds":[{"step":"migrate"}]}]}`), 0o644))
		statusPath := filepath.Join(dir, "migration-loop-status.json")
		assert.NoError(t, os.WriteFile(statusPath, []byte(`{"updatedAt":"2026-01-01T00:00:00Z","tenants":[{"name":"tenants-1","failed":true,"steps":[{"step":"migrate","status":"failed"}]}]}`), 0o644))

		s, err := ReadLoopSummary(loopPath)
		assert.NoError(t, err)
		assert.Equal(t, 0, s.Done)
		assert.Equal(t, 1, s.Failed)
		assert.False(t, s.Complete)
	})
}

func TestDeriveTenantStatus(t *testing.T) {
	f := func(t *testing.T, tenant schema.MigrationLoopTenantStatus, want string) {
		t.Helper()
		assert.Equal(t, want, deriveTenantStatus(tenant), "steps=%+v completed=%v", tenant.Steps, tenant.Completed)
	}
	f(t, schema.MigrationLoopTenantStatus{Completed: true}, StatusCompleted)
	f(t, schema.MigrationLoopTenantStatus{Completed: true, Steps: []schema.MigrationLoopStepStatus{{Status: StatusFailed}}}, StatusCompleted)
	f(t, schema.MigrationLoopTenantStatus{Steps: []schema.MigrationLoopStepStatus{
		{Status: StatusPending}, {Status: StatusRunning},
	}}, StatusRunning)
	f(t, schema.MigrationLoopTenantStatus{Steps: []schema.MigrationLoopStepStatus{
		{Status: StatusFailed}, {Status: StatusPending},
	}}, StatusFailed)
	f(t, schema.MigrationLoopTenantStatus{Steps: []schema.MigrationLoopStepStatus{
		{Status: StatusFailed}, {Status: StatusRunning},
	}}, StatusRunning)
	f(t, schema.MigrationLoopTenantStatus{}, StatusPending)
	f(t, schema.MigrationLoopTenantStatus{Steps: []schema.MigrationLoopStepStatus{
		{Status: StatusCompleted},
	}}, StatusPending)
}

func TestCurrentAttempt(t *testing.T) {
	f := func(t *testing.T, tenant schema.MigrationLoopTenantStatus, want int) {
		t.Helper()
		assert.Equal(t, want, currentAttempt(tenant), "steps=%+v", tenant.Steps)
	}
	f(t, schema.MigrationLoopTenantStatus{}, 0)
	f(t, schema.MigrationLoopTenantStatus{Steps: []schema.MigrationLoopStepStatus{
		{Step: "manifest", Status: StatusCompleted, Attempts: 2},
		{Step: "migrate", Status: StatusRunning, Attempts: 3},
		{Step: "verify", Status: StatusPending},
	}}, 3)
	f(t, schema.MigrationLoopTenantStatus{Steps: []schema.MigrationLoopStepStatus{
		{Step: "manifest", Status: StatusCompleted, Attempts: 1},
		{Step: "migrate", Status: StatusCompleted, Attempts: 4},
	}}, 4)
}

func TestComputeMigrateRate(t *testing.T) {
	now := time.Date(2026, 8, 17, 12, 0, 0, 0, time.UTC)
	start := now.Add(-10 * time.Second)
	afterStart := now.Add(-5 * time.Second)
	beforeStart := now.Add(-20 * time.Second)
	bytes := func(n int64) *int64 { return new(n) }

	runningTenant := schema.MigrationLoopTenantStatus{Steps: []schema.MigrationLoopStepStatus{
		{Step: "migrate", Status: StatusRunning, StartedAt: &start},
	}}
	shards := []schema.Shard{
		{Phase: schema.PhaseImported, FinishedAt: &afterStart, Bytes: bytes(600)},
		{Phase: schema.PhaseImported, FinishedAt: &afterStart, Bytes: bytes(400)},
		{Phase: schema.PhaseImported, FinishedAt: &beforeStart, Bytes: bytes(9999)},
		{Phase: schema.PhasePending, FinishedAt: &afterStart, Bytes: bytes(9999)},
		{Phase: schema.PhaseImported, FinishedAt: &afterStart},
	}
	assert.Equal(t, float64(100), computeMigrateRate(runningTenant, shards, now))

	f := func(t *testing.T, tenant schema.MigrationLoopTenantStatus, shards []schema.Shard, now time.Time) {
		t.Helper()
		assert.Zero(t, computeMigrateRate(tenant, shards, now))
	}
	f(t, schema.MigrationLoopTenantStatus{}, shards, now) // no migrate step
	f(t, schema.MigrationLoopTenantStatus{Steps: []schema.MigrationLoopStepStatus{
		{Step: "migrate", Status: StatusCompleted, StartedAt: &start},
	}}, shards, now) // not running
	f(t, schema.MigrationLoopTenantStatus{Steps: []schema.MigrationLoopStepStatus{
		{Step: "migrate", Status: StatusRunning},
	}}, shards, now) // running but never started
	f(t, runningTenant, nil, now)      // no shard bytes yet
	f(t, runningTenant, shards, start) // zero elapsed
}

func TestTenantDurationSeconds(t *testing.T) {
	now := time.Date(2026, 8, 17, 12, 0, 0, 0, time.UTC)
	start := now.Add(-90 * time.Second)

	assert.Zero(t, tenantDurationSeconds(schema.MigrationLoopTenantStatus{}, now), "never started")

	tenant := schema.MigrationLoopTenantStatus{StartedAt: &start}
	assert.Equal(t, float64(90), tenantDurationSeconds(tenant, now), "still running counts up to now")

	fin := now.Add(-30 * time.Second)
	tenant.FinishedAt = &fin
	assert.Equal(t, float64(60), tenantDurationSeconds(tenant, now), "finished caps duration at FinishedAt")
}
