package tenants

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

	"github.com/stretchr/testify/assert"
)

func writeMergeInput(t *testing.T, path string, tenants []string) {
	t.Helper()
	entries := make([]Entry, 0, len(tenants))
	for _, tn := range tenants {
		entries = append(entries, Entry{Tenant: tn, Source: SourceK8s})
	}
	assert.NoError(t, WriteEntriesFile(path, entries, nil))
}

func decodeFlatTenants(t *testing.T, path string) []string {
	t.Helper()
	var got []string
	assert.NoError(t, json.Unmarshal(mustReadFile(t, path), &got))
	return got
}

func mustReadFile(t *testing.T, path string) []byte {
	t.Helper()
	b, err := os.ReadFile(path)
	assert.NoError(t, err)
	return b
}

func TestRunMergeSingleInput(t *testing.T) {
	dir := t.TempDir()
	in := filepath.Join(dir, "tenants-all.json")
	writeMergeInput(t, in, []string{"1001:0", "1002:0", "1003:0"})

	out := filepath.Join(dir, "tenants.json")
	opts := MergeOptions{Inputs: []string{in}, Output: out}
	var stdout, stderr bytes.Buffer
	assert.NoError(t, RunMerge(context.Background(), opts, &stdout, &stderr))

	assert.Equal(t, []string{"1001:0", "1002:0", "1003:0"}, decodeFlatTenants(t, out))
}

func TestRunMergeMergesInputs(t *testing.T) {
	dir := t.TempDir()
	in1 := filepath.Join(dir, "tenants-k8s.json")
	in2 := filepath.Join(dir, "tenants-s3.json")
	writeMergeInput(t, in1, []string{"1001:0"})
	writeMergeInput(t, in2, []string{"1002:0"})

	out := filepath.Join(dir, "tenants.json")
	opts := MergeOptions{Inputs: []string{in1, in2}, Output: out}
	var stdout, stderr bytes.Buffer
	assert.NoError(t, RunMerge(context.Background(), opts, &stdout, &stderr))

	assert.Equal(t, []string{"1001:0", "1002:0"}, decodeFlatTenants(t, out))
}

// RunMerge emits a flat []string and drops Source, so source priority is
// observable only at the mergeEntries layer.
func TestMergeEntriesArchiveWinsOnCollision(t *testing.T) {
	merged := mergeEntries([]Entry{
		{Tenant: "1001:0", Source: SourceK8s},
		{Tenant: "1001:0", Source: SourceS3},
	})
	assert.Len(t, merged, 1)
	assert.Equal(t, SourceS3, merged[0].Source)
}

func TestRunMergeStdoutWhenNoOutput(t *testing.T) {
	dir := t.TempDir()
	in := filepath.Join(dir, "tenants-all.json")
	writeMergeInput(t, in, []string{"1001:0"})

	opts := MergeOptions{Inputs: []string{in}, Output: ""}
	var stdout, stderr bytes.Buffer
	assert.NoError(t, RunMerge(context.Background(), opts, &stdout, &stderr))

	var got []string
	assert.NoError(t, json.Unmarshal(stdout.Bytes(), &got))
	assert.Equal(t, []string{"1001:0"}, got)
}

func TestRunMergeEmptyError(t *testing.T) {
	dir := t.TempDir()
	in := filepath.Join(dir, "tenants-empty.json")
	writeMergeInput(t, in, nil)

	opts := MergeOptions{Inputs: []string{in}, Output: filepath.Join(dir, "tenants.json")}
	assert.Error(t, RunMerge(context.Background(), opts, &bytes.Buffer{}, &bytes.Buffer{}))
}

func writeMergeInputSource(t *testing.T, path string, tenants []string, source string) {
	t.Helper()
	entries := make([]Entry, 0, len(tenants))
	for _, tn := range tenants {
		entries = append(entries, Entry{Tenant: tn, Source: source})
	}
	assert.NoError(t, WriteEntriesFile(path, entries, nil))
}

func TestRunMergeReportsDuplicates(t *testing.T) {
	dir := t.TempDir()
	k8s := filepath.Join(dir, "tenants-k8s.json")
	s3 := filepath.Join(dir, "tenants-s3.json")
	writeMergeInputSource(t, k8s, []string{"1001:0", "1002:0"}, SourceK8s)
	writeMergeInputSource(t, s3, []string{"1001:0", "1003:0"}, SourceS3)

	out := filepath.Join(dir, "tenants.json")
	opts := MergeOptions{Inputs: []string{k8s, s3}, Output: out}
	var stdout, stderr bytes.Buffer
	assert.NoError(t, RunMerge(context.Background(), opts, &stdout, &stderr))

	assert.Equal(t, []string{"1001:0", "1002:0", "1003:0"}, decodeFlatTenants(t, out))
	assert.Contains(t, stderr.String(), "duplicate: tenant=1001:0 kept="+s3+" dropped="+k8s)
	assert.Contains(t, stderr.String(), "merge summary: loaded=4 duplicates=1 merged=3")
}

func TestRunMergeDuplicatePriorityBeatsOrder(t *testing.T) {
	dir := t.TempDir()
	s3 := filepath.Join(dir, "tenants-s3.json")
	k8s := filepath.Join(dir, "tenants-k8s.json")
	writeMergeInputSource(t, s3, []string{"1001:0"}, SourceS3)
	writeMergeInputSource(t, k8s, []string{"1001:0"}, SourceK8s)

	opts := MergeOptions{Inputs: []string{s3, k8s}, Output: filepath.Join(dir, "tenants.json")}
	var stdout, stderr bytes.Buffer
	assert.NoError(t, RunMerge(context.Background(), opts, &stdout, &stderr))
	assert.Contains(t, stderr.String(), "duplicate: tenant=1001:0 kept="+s3+" dropped="+k8s)
}

func TestRunMergeReportsIntraInputDuplicate(t *testing.T) {
	dir := t.TempDir()
	in := filepath.Join(dir, "tenants-k8s.json")
	entries := []Entry{{Tenant: "1001:0", Source: SourceK8s}, {Tenant: "1001:0", Source: SourceK8s}}
	assert.NoError(t, WriteEntriesFile(in, entries, nil))

	opts := MergeOptions{Inputs: []string{in}, Output: filepath.Join(dir, "tenants.json")}
	var stdout, stderr bytes.Buffer
	assert.NoError(t, RunMerge(context.Background(), opts, &stdout, &stderr))
	assert.Contains(t, stderr.String(), "duplicate: tenant=1001:0 kept="+in+" dropped="+in)
	assert.Contains(t, stderr.String(), "merge summary: loaded=2 duplicates=1 merged=1")
}

func TestRunMergeSummaryWithoutDuplicates(t *testing.T) {
	dir := t.TempDir()
	in := filepath.Join(dir, "tenants-all.json")
	writeMergeInput(t, in, []string{"1001:0", "1002:0"})

	opts := MergeOptions{Inputs: []string{in}, Output: filepath.Join(dir, "tenants.json")}
	var stdout, stderr bytes.Buffer
	assert.NoError(t, RunMerge(context.Background(), opts, &stdout, &stderr))
	assert.NotContains(t, stderr.String(), "duplicate:")
	assert.Contains(t, stderr.String(), "merge summary: loaded=2 duplicates=0 merged=2")
}

func TestMergeEntriesTrackedReportsOrigin(t *testing.T) {
	entries := []Entry{
		{Tenant: "1001:0", Source: SourceK8s},
		{Tenant: "1002:0", Source: SourceS3},
		{Tenant: "1001:0", Source: SourceS3},
	}
	merged, origin, index := mergeEntriesTracked(entries)
	assert.Len(t, merged, 2)
	assert.Equal(t, SourceS3, merged[index["1001:0"]].Source)
	assert.Equal(t, 2, origin[index["1001:0"]])
	assert.Equal(t, 1, origin[index["1002:0"]])
}
