package maintenance

import (
	"testing"

	"github.com/seaweedfs/seaweedfs/weed/pb/worker_pb"
)

// stubConfigPersistence implements the LoadXTaskPolicy accessors that the task config loaders
// type-assert on. The real implementation is *dash.ConfigPersistence, which this package cannot
// import: weed/admin/dash already imports weed/admin/maintenance, so the dependency only runs one
// way and the persistence argument has to stay duck-typed.
type stubConfigPersistence struct {
	vacuum    *worker_pb.TaskPolicy
	ec        *worker_pb.TaskPolicy
	balance   *worker_pb.TaskPolicy
	ecBalance *worker_pb.TaskPolicy
}

func (s *stubConfigPersistence) LoadVacuumTaskPolicy() (*worker_pb.TaskPolicy, error) {
	return s.vacuum, nil
}

func (s *stubConfigPersistence) LoadErasureCodingTaskPolicy() (*worker_pb.TaskPolicy, error) {
	return s.ec, nil
}

func (s *stubConfigPersistence) LoadBalanceTaskPolicy() (*worker_pb.TaskPolicy, error) {
	return s.balance, nil
}

func (s *stubConfigPersistence) LoadEcBalanceTaskPolicy() (*worker_pb.TaskPolicy, error) {
	return s.ecBalance, nil
}

func disabledStub() *stubConfigPersistence {
	return &stubConfigPersistence{
		vacuum: &worker_pb.TaskPolicy{
			Enabled:               false,
			MaxConcurrent:         2,
			RepeatIntervalSeconds: 2 * 3600,
			TaskConfig: &worker_pb.TaskPolicy_VacuumConfig{
				VacuumConfig: &worker_pb.VacuumTaskConfig{GarbageThreshold: 0.3, MinVolumeAgeHours: 24},
			},
		},
		ec: &worker_pb.TaskPolicy{
			Enabled:               false,
			MaxConcurrent:         1,
			RepeatIntervalSeconds: 3600,
			TaskConfig: &worker_pb.TaskPolicy_ErasureCodingConfig{
				ErasureCodingConfig: &worker_pb.ErasureCodingTaskConfig{FullnessRatio: 0.95, QuietForSeconds: 3600, MinVolumeSizeMb: 30},
			},
		},
		balance: &worker_pb.TaskPolicy{
			Enabled:               false,
			MaxConcurrent:         1,
			RepeatIntervalSeconds: 30 * 60,
			TaskConfig: &worker_pb.TaskPolicy_BalanceConfig{
				BalanceConfig: &worker_pb.BalanceTaskConfig{ImbalanceThreshold: 0.2, MinServerCount: 7},
			},
		},
		ecBalance: &worker_pb.TaskPolicy{
			Enabled:               false,
			MaxConcurrent:         1,
			RepeatIntervalSeconds: 60 * 60,
			TaskConfig: &worker_pb.TaskPolicy_EcBalanceConfig{
				EcBalanceConfig: &worker_pb.EcBalanceTaskConfig{ImbalanceThreshold: 0.2, MinServerCount: 5},
			},
		},
	}
}

// TestBuildPolicyFromTaskConfigsUsesPersistence covers a reported bug: the persistence argument used to be a
// literal nil, which no type assertion can satisfy, so a task disabled on disk came back enabled.
func TestBuildPolicyFromTaskConfigsUsesPersistence(t *testing.T) {
	policy := BuildPolicyFromTaskConfigs(disabledStub())

	for _, taskType := range []string{"vacuum", "erasure_coding", "balance", "ec_balance"} {
		taskPolicy := policy.TaskPolicies[taskType]
		if taskPolicy == nil {
			t.Fatalf("no %s task policy built", taskType)
		}
		if taskPolicy.Enabled {
			t.Errorf("%s enabled = true, want false from the persisted config", taskType)
		}
	}

	if got := policy.TaskPolicies["balance"].GetBalanceConfig().GetMinServerCount(); got != 7 {
		t.Errorf("balance min server count = %d, want persisted 7", got)
	}
}

// TestBuildPolicyFromTaskConfigsWithoutPersistence keeps the documented fallback: with no config
// store there is nothing to read, so the compiled-in defaults apply.
func TestBuildPolicyFromTaskConfigsWithoutPersistence(t *testing.T) {
	policy := BuildPolicyFromTaskConfigs(nil)

	for _, taskType := range []string{"vacuum", "erasure_coding", "balance", "ec_balance"} {
		taskPolicy := policy.TaskPolicies[taskType]
		if taskPolicy == nil {
			t.Fatalf("no %s task policy built", taskType)
		}
		if !taskPolicy.Enabled {
			t.Errorf("%s enabled = false, want the compiled-in default true", taskType)
		}
	}
}

// TestNewMaintenanceManagerUsesPersistenceForPolicyFallback covers the path the admin server takes
// when no maintenance.pb has been written yet: the config carries no policy, so the manager builds
// one itself and must read the persisted task configs to do it.
func TestNewMaintenanceManagerUsesPersistenceForPolicyFallback(t *testing.T) {
	config := DefaultMaintenanceConfig()
	if config.Policy != nil {
		t.Fatal("default config unexpectedly carries a policy, test no longer covers the fallback")
	}

	manager := NewMaintenanceManager(nil, config, disabledStub())

	policy := manager.queue.policy
	if policy == nil {
		t.Fatal("queue policy is nil")
	}
	if IsTaskEnabled(policy, MaintenanceTaskType("balance")) {
		t.Error("balance enabled = true, want false from the persisted config")
	}
	if IsTaskEnabled(policy, MaintenanceTaskType("vacuum")) {
		t.Error("vacuum enabled = true, want false from the persisted config")
	}
	if manager.scanner.policy != policy {
		t.Error("scanner and queue disagree on the policy")
	}
}
