package migration

import (
	"bytes"
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"

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

func writeTenantsFile(t *testing.T, dir string) string {
	t.Helper()
	path := filepath.Join(dir, "tenants-all.json")
	content := `{
  "total": 3,
  "tenants": [
    {"tenant": "1001:0", "bizType": "tidbcloud", "source": "k8s"},
    {"tenant": "1002:0", "bizType": "tidbcloud", "source": "k8s"},
    {"tenant": "1003:42", "bizType": "tidbcloud", "source": "s3"}
  ]
}`
	assert.NoError(t, os.WriteFile(path, []byte(content), 0o644))
	return path
}

func TestRunTimed(t *testing.T) {
	dir := t.TempDir()
	tenantsFile := writeTenantsFile(t, dir)
	manifestPath := filepath.Join(dir, "tenants-all-manifest.json")

	opts := ManifestOptions{
		Region:         "prod-aws-us-east-1",
		Vmcluster:      "vmcluster",
		TenantsFile:    tenantsFile,
		SrcVmselectURL: "http://vmselect-migration.observability.svc.cluster.local:8481/select/{tenant}/prometheus",
		DstVminsertURL: "http://vminsert-vmcluster.observability.svc.cluster.local:8480/insert/{tenant}/prometheus",
		Start:          "2026-06-16T00:00:00Z",
		End:            "2026-06-18T00:00:00Z",
		StepInterval:   "1d",
		Output:         manifestPath,
	}
	var stdout bytes.Buffer
	assert.NoError(t, BuildManifest(context.Background(), opts, ManifestOutput{Progress: &stdout}))
	data, err := os.ReadFile(manifestPath)
	assert.NoError(t, err)
	var m schema.Manifest
	assert.NoError(t, json.Unmarshal(data, &m))

	assert.Equal(t, "prod-aws-us-east-1", m.Region)
	assert.Equal(t, "1d", m.StepInterval)
	assert.Len(t, m.Tenants, 3)
	first := m.Tenants[0]
	assert.Equal(t, "1001:0", first.Tenant)
	assert.Equal(t, "http://vmselect-migration.observability.svc.cluster.local:8481/select/1001:0/prometheus", first.SourceAddr)
	assert.Equal(t, "http://vminsert-vmcluster.observability.svc.cluster.local:8480/insert/1001:0/prometheus", first.DestinationAddr)
	assert.Len(t, first.Shards, 2)
	assert.Equal(t, "1001:0:1", first.Shards[0].ShardID)
	assert.Equal(t, "2026-06-17T00:00:00Z", first.Shards[0].TimeStart)
	assert.Equal(t, "2026-06-18T00:00:00Z", first.Shards[0].TimeEnd)
	assert.Equal(t, "2026-06-16T00:00:00Z", first.Shards[1].TimeStart)

	third := m.Tenants[2]
	assert.Equal(t, "1003:42", third.Tenant)
	assert.Contains(t, third.SourceAddr, "/1003:42/prometheus")

	statusPath := filepath.Join(dir, "tenants-all-status.json")
	doc, err := status.LoadDocument(statusPath)
	assert.NoError(t, err)
	assert.Equal(t, 6, doc.Summary.Total)
	assert.Equal(t, 6, doc.Summary.Pending)
}

func TestValidateStartEnd(t *testing.T) {
	cases := []struct {
		name string
		opts ManifestOptions
		want string
	}{
		{"end before start", ManifestOptions{Start: "2026-06-18T00:00:00Z", End: "2026-06-16T00:00:00Z"}, "later than"},
		{"bad step interval", ManifestOptions{Start: "2026-06-16T00:00:00Z", End: "2026-06-18T00:00:00Z", StepInterval: "5x"}, "step interval"},
		{"bad start", ManifestOptions{Start: "yesterday", End: "2026-06-18T00:00:00Z"}, "--start"},
		{"bad end", ManifestOptions{Start: "2026-06-16T00:00:00Z", End: "tomorrow"}, "--end"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			_, _, _, err := tc.opts.Validate()
			assert.Error(t, err)
			assert.ErrorContains(t, err, tc.want)
		})
	}
}

func TestRunStdoutOutput(t *testing.T) {
	dir := t.TempDir()
	tenantsFile := writeTenantsFile(t, dir)

	opts := ManifestOptions{
		Region:         "prod",
		TenantsFile:    tenantsFile,
		SrcVmselectURL: "http://vmselect-migration.observability.svc.cluster.local:8481/select/{tenant}/prometheus",
		DstVminsertURL: "http://vminsert-vmcluster.observability.svc.cluster.local:8480/insert/{tenant}/prometheus",
		Start:          "2026-06-16T00:00:00Z",
		End:            "2026-06-18T00:00:00Z",
	}
	var stdout bytes.Buffer
	assert.NoError(t, BuildManifest(context.Background(), opts, ManifestOutput{Result: &stdout}))
	assert.Contains(t, stdout.String(), `"tenant": "1001:0"`)
}

func TestParseDuration(t *testing.T) {
	f := func(in string, wantSec int, wantErr bool) {
		t.Helper()
		got, err := parseDuration(in)
		if wantErr {
			assert.Error(t, err, "in=%q", in)
			return
		}
		assert.NoError(t, err, "in=%q", in)
		assert.Equal(t, wantSec, int(got.Seconds()), "in=%q", in)
	}
	f("1d", 86400, false)
	f("2h", 7200, false)
	f("30m", 1800, false)
	f("7d", 7*86400, false)
	f("1w", 7*86400, false)
	f("1y", 365*86400, false)
	f("2h30m", 2*3600+30*60, false)
	f("1d12h", 36*3600, false)
	f("90s", 90, false)
	f("500ms", 0, false)
	f("0d", 0, true)
	f("-1d", 0, true)
	f("5x", 0, true)
	f("", 0, true)
	f("abc", 0, true)
}
