package plan

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"

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

// Load reads and validates source or compiled plan JSON.
func Load(path string) (*Config, Stages, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil, nil, fmt.Errorf("%s not found. Generate one via `vm-migration plan init`: %w", path, err)
		}
		return nil, nil, fmt.Errorf("%s: %w", path, err)
	}
	return LoadBytes(data)
}

// LoadBytes parses and validates source or compiled plan JSON from memory.
func LoadBytes(data []byte) (*Config, Stages, error) {
	var raw schema.RawPlan
	if err := json.Unmarshal(data, &raw); err != nil {
		return nil, nil, err
	}
	if err := validatePlan(&raw); err != nil {
		return nil, nil, err
	}
	cfg := buildConfig(&raw)
	stages := buildStages(&raw)
	return cfg, stages, nil
}

// buildConfig projects the validated RawPlan into the Config struct that
// downstream code uses.
//
// No type assertions — fields are typed at unmarshal.
func buildConfig(raw *schema.RawPlan) *Config {
	cfg := &Config{
		SourceReplicaCount:          raw.SourceReplicaCount,
		TargetReplicaCount:          raw.TargetReplicaCount,
		DrainOrdinals:               rangeInts(raw.TargetReplicaCount, raw.SourceReplicaCount),
		RetainOrdinals:              rangeInts(0, raw.TargetReplicaCount),
		Namespace:                   raw.Namespace,
		VmselectURLPattern:          schema.VmselectURLPattern(raw.Namespace, schema.VmselectName),
		VmselectMigrationURLPattern: schema.VmselectURLPattern(raw.Namespace, schema.VmselectMigrationName),
		VmselectCandidateURLPattern: schema.VmselectURLPattern(raw.Namespace, schema.VmselectCandidateName),
		VminsertURLPattern:          schema.VminsertURLPattern(raw.Namespace, schema.VminsertName),
		VmselectImage:               raw.Vmselects.Image,
		VmselectMigration:           sizingFromRaw(raw.Vmselects.Migration),
		VmselectCandidate:           sizingFromRaw(raw.Vmselects.Candidate),
		Region:                      raw.Region,
		WindowStart:                 raw.RetentionWindow.Start,
		WindowEnd:                   raw.RetentionWindow.End,
		ShardStepInterval:           raw.ShardStepInterval,
		VmctlRateLimit:              raw.Vmctl.RateLimitBytes,
		VmctlConcurrency:            raw.Vmctl.Concurrency,
		VmctlStepInterval:           raw.Vmctl.StepInterval,
		Tenants:                     append([]string(nil), raw.Tenants...),
	}
	if cfg.VmctlStepInterval == "" {
		cfg.VmctlStepInterval = VmctlDefaultStepInterval
	}
	return cfg
}

// buildStages flattens RawPlan.Steps into the Stages map keyed by stage name.
func buildStages(raw *schema.RawPlan) Stages {
	return Stages{
		StagePrepare:      raw.Steps.Prepare.Items,
		StageMigration:    raw.Steps.Migration.Items,
		StageDecommission: raw.Steps.Decommission.Items,
	}
}

func sizingFromRaw(t schema.RawVmselectTarget) TargetSizing {
	return TargetSizing{
		Replicas:      t.Replicas,
		CPURequest:    t.Resources.Requests.CPU,
		MemoryRequest: t.Resources.Requests.Memory,
		CPULimit:      t.Resources.Limits.CPU,
		MemoryLimit:   t.Resources.Limits.Memory,
	}
}
