package plan

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"

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

func TestRunWritesCompiledPlanWithTenants(t *testing.T) {
	dir := t.TempDir()
	golden := filepath.Join("..", "..", "testdata", "golden", "plan-input.json")
	planBytes, err := os.ReadFile(golden)
	assert.NoError(t, err)
	planPath := filepath.Join(dir, "json")
	assert.NoError(t, os.WriteFile(planPath, planBytes, 0o644))
	tenantsPath := filepath.Join(dir, "tenants.json")
	assert.NoError(t, os.WriteFile(tenantsPath, []byte(`["1002:0", "1001:0"]`), 0o644))

	result, err := Compile(CompileOptions{Plan: planPath, Tenants: tenantsPath})
	assert.NoError(t, err)

	_, err = os.Stat(result.CompilePath)
	assert.NoError(t, err, "compiled plan")
	data, err := os.ReadFile(result.CompilePath)
	assert.NoError(t, err)
	var compiled schema.CompiledDoc
	assert.NoError(t, json.Unmarshal(data, &compiled))
	assert.Equal(t, "1001:0,1002:0", strings.Join(compiled.Tenants, ","), "sorted flat tenant list")
	var wire map[string]json.RawMessage
	assert.NoError(t, json.Unmarshal(data, &wire))
	_, ok := wire["migrationLoop"]
	assert.False(t, ok, "compiled plan must not contain migrationLoop")
	for _, name := range []string{"migration-loop.json", "tenants-1001-0.json", "tenants-1002-0.json"} {
		_, err := os.Stat(filepath.Join(dir, name))
		assert.Error(t, err, "runtime artifact %s must not be written", name)
	}
}

func TestRunHonorsOutput(t *testing.T) {
	dir := t.TempDir()
	golden := filepath.Join("..", "..", "testdata", "golden", "plan-input.json")
	planBytes, err := os.ReadFile(golden)
	assert.NoError(t, err)
	planPath := filepath.Join(dir, "json")
	assert.NoError(t, os.WriteFile(planPath, planBytes, 0o644))
	tenantsPath := filepath.Join(dir, "tenants.json")
	assert.NoError(t, os.WriteFile(tenantsPath, []byte(`["1001:0"]`), 0o644))
	output := filepath.Join(dir, "review.json")
	result, err := Compile(CompileOptions{Plan: planPath, Tenants: tenantsPath, Output: output})
	assert.NoError(t, err)
	assert.Equal(t, output, result.CompilePath)
}

func TestRunRejectsEmptyTenants(t *testing.T) {
	dir := t.TempDir()
	golden := filepath.Join("..", "..", "testdata", "golden", "plan-input.json")
	planPath := filepath.Join(dir, "json")
	data, err := os.ReadFile(golden)
	assert.NoError(t, err)
	assert.NoError(t, os.WriteFile(planPath, data, 0o644))
	emptyTenants := filepath.Join(dir, "tenants.json")
	assert.NoError(t, os.WriteFile(emptyTenants, []byte(`[]`), 0o644))

	_, err = Compile(CompileOptions{Plan: planPath, Tenants: emptyTenants})
	assert.Error(t, err)
	assert.ErrorContains(t, err, "no tenants")
}

func TestWritePerTenantFiles(t *testing.T) {
	dir := t.TempDir()
	loopPlan := &schema.MigrationLoopPlan{Tenants: []schema.MigrationLoopTenant{
		{Name: "t-one", TenantsFile: "tenants-one.json"},
		{Name: "t-two", TenantsFile: "tenants-two.json"},
	}}
	assert.NoError(t, WritePerTenantFiles(dir, loopPlan))
	for _, f := range []struct{ file, want string }{
		{"tenants-one.json", `"t-one"`},
		{"tenants-two.json", `"t-two"`},
	} {
		data, err := os.ReadFile(filepath.Join(dir, f.file))
		assert.NoError(t, err, f.file)
		assert.Contains(t, string(data), f.want, f.file)
	}

	blocker := filepath.Join(dir, "blocked.json")
	assert.NoError(t, os.Mkdir(blocker, 0o755))
	err := WritePerTenantFiles(dir, &schema.MigrationLoopPlan{Tenants: []schema.MigrationLoopTenant{
		{Name: "x", TenantsFile: "blocked.json"},
	}})
	assert.ErrorContains(t, err, "write "+blocker)
}

func TestWriteLoopDocument(t *testing.T) {
	dir := t.TempDir()
	loopPlan := &schema.MigrationLoopPlan{Tenants: []schema.MigrationLoopTenant{{Name: "t1", TenantsFile: "t1.json"}}}
	path, err := WriteLoopDocument(dir, loopPlan)
	assert.NoError(t, err)
	assert.Equal(t, filepath.Join(dir, "migration-loop.json"), path)
	var got schema.MigrationLoopPlan
	assert.NoError(t, jsonio.ReadFile(path, &got))
	assert.Equal(t, "t1", got.Tenants[0].Name)

	blocker := filepath.Join(dir, "occupied")
	assert.NoError(t, os.WriteFile(blocker, []byte("x"), 0o644))
	_, err = WriteLoopDocument(blocker, loopPlan)
	assert.Error(t, err)
}
