package tenants

import (
	"context"
	"errors"
	"fmt"
	"io"

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

// MergeOptions carries inputs to tenant-list merging.
type MergeOptions struct {
	Inputs []string
	Output string // empty = stdout
}

// RunMerge reads one or more tenants-*.json files (each in the canonical
// {"total": N, "tenants": [...]} wire shape produced by tenants dedicated /
// essential), merges by tenant ID (archive sources win over k8s), sorts
// numerically, and writes a flat array of tenant id strings to opts.Output.
//
// Every duplicate tenant is reported to stderr (kept and dropped input) plus
// a loaded/duplicates/merged summary, so per-input counts that do not add up
// are explainable.
//
// The output format aligns with what plan compile consumes via --tenants. The
// compiled plan embeds the ids; per-tenant splitting happens in prepare.
func RunMerge(ctx context.Context, opts MergeOptions, stdout, stderr io.Writer) error {
	var collected []Entry
	// srcInput[i] is the index into opts.Inputs that collected[i] came from.
	var srcInput []int
	for i, path := range opts.Inputs {
		entries, err := ReadEntriesFile(path)
		if err != nil {
			return fmt.Errorf("--input %s: %w", path, err)
		}
		printer.Printf(stderr, "  loaded %d tenant(s) from %s", len(entries), path)
		collected = append(collected, entries...)
		for range entries {
			srcInput = append(srcInput, i)
		}
	}
	if len(collected) == 0 {
		return errors.New("no tenants in input file(s)")
	}
	merged, origin, index := mergeEntriesTracked(collected)

	// Survivors are the collected entries still occupying a merged slot; the
	// rest were shadowed by a higher-priority duplicate.
	surviving := make([]bool, len(collected))
	for _, o := range origin {
		surviving[o] = true
	}
	duplicates := 0
	for i := range collected {
		if surviving[i] {
			continue
		}
		duplicates++
		j := index[collected[i].Tenant]
		printer.Printf(stderr, "  duplicate: tenant=%s kept=%s dropped=%s",
			collected[i].Tenant, opts.Inputs[srcInput[origin[j]]], opts.Inputs[srcInput[i]])
	}
	printer.Printf(stderr, "  merge summary: loaded=%d duplicates=%d merged=%d",
		len(collected), duplicates, len(merged))

	sortByTenant(merged)
	tenantIDs := make([]string, len(merged))
	for i, e := range merged {
		tenantIDs[i] = e.Tenant
	}

	if opts.Output == "" {
		data, err := jsonio.MarshalIndent(tenantIDs)
		if err != nil {
			return fmt.Errorf("marshal tenants: %w", err)
		}
		_, err = stdout.Write(data)
		return err
	}
	if err := jsonio.WriteFile(opts.Output, tenantIDs); err != nil {
		return fmt.Errorf("write %s: %w", opts.Output, err)
	}
	printer.Printf(stderr, "  wrote %d tenant(s) to %s", len(tenantIDs), opts.Output)
	return nil
}
