package service

import (
	"context"
	"crypto/rand"
	"fmt"
	"math/big"

	"oci-portal/internal/oci"
)

// AvailabilityDomains 列出区域内可用域。
func (s *OciConfigService) AvailabilityDomains(ctx context.Context, id uint, region string) ([]string, error) {
	cred, err := s.credentialsByID(ctx, id)
	if err != nil {
		return nil, err
	}
	return s.client.ListAvailabilityDomains(ctx, cred, region)
}

// Instances 列出实例（含 IP 补全）；compartmentID 为空表示租户根。
func (s *OciConfigService) Instances(ctx context.Context, id uint, region, compartmentID string) ([]oci.Instance, error) {
	cred, err := s.credentialsInCompartment(ctx, id, compartmentID)
	if err != nil {
		return nil, err
	}
	return s.client.ListInstances(ctx, cred, region)
}

// maxBatchCreate 是单次批量创建实例的数量上限。
const maxBatchCreate = 10

// CreateInstances 批量创建实例，逐台尝试互不影响；count 大于 1 时
// 名称自动追加 -1、-2 等序号。返回成功实例与逐台失败信息。
// in.CompartmentID 为空时建在租户根。
func (s *OciConfigService) CreateInstances(ctx context.Context, id uint, in oci.CreateInstanceInput, count int) ([]oci.Instance, []string, error) {
	if count < 1 || count > maxBatchCreate {
		return nil, nil, fmt.Errorf("create instances: count must be between 1 and %d", maxBatchCreate)
	}
	if err := validateCreateInstance(in); err != nil {
		return nil, nil, err
	}
	cred, err := s.credentialsInCompartment(ctx, id, in.CompartmentID)
	if err != nil {
		return nil, nil, err
	}
	instances := make([]oci.Instance, 0, count)
	var failures []string
	for i := 1; i <= count; i++ {
		one := in
		if count > 1 {
			one.DisplayName = fmt.Sprintf("%s-%d", in.DisplayName, i)
		}
		if err := applyGeneratedRootPassword(&one); err != nil {
			return nil, nil, err
		}
		instance, err := s.client.LaunchInstance(ctx, cred, one)
		if err != nil {
			failures = append(failures, fmt.Sprintf("%s: %s", one.DisplayName, oci.CompactError(err)))
			continue
		}
		instances = append(instances, instance)
	}
	return instances, failures, nil
}

// applyGeneratedRootPassword 在开启随机密码时为单台实例生成 root 密码，
// 同时写入 FreeformTags["RootPassword"] 便于面板回显；批量创建时每台独立。
func applyGeneratedRootPassword(one *oci.CreateInstanceInput) error {
	if !one.GenerateRootPassword {
		return nil
	}
	pwd, err := randomPassword(16)
	if err != nil {
		return fmt.Errorf("generate root password: %w", err)
	}
	one.RootPassword = pwd
	tags := make(map[string]string, len(one.FreeformTags)+1)
	for k, v := range one.FreeformTags {
		tags[k] = v
	}
	tags["RootPassword"] = pwd
	one.FreeformTags = tags
	return nil
}

// randomPassword 生成含大小写字母、数字与安全符号的随机密码；
// 字符集避开引号、冒号等会破坏 cloud-init chpasswd 语法的字符。
func randomPassword(length int) (string, error) {
	const charset = "abcdefghijkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789@#%^*-_=+"
	out := make([]byte, length)
	for i := range out {
		n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
		if err != nil {
			return "", err
		}
		out[i] = charset[n.Int64()]
	}
	return string(out), nil
}

func validateCreateInstance(in oci.CreateInstanceInput) error {
	switch {
	case in.DisplayName == "":
		return fmt.Errorf("create instance: displayName is required")
	case in.Shape == "":
		return fmt.Errorf("create instance: shape is required")
	case in.ImageID == "" && in.BootVolumeID == "":
		return fmt.Errorf("create instance: imageId or bootVolumeId is required")
	case in.ImageID != "" && in.BootVolumeID != "":
		return fmt.Errorf("create instance: imageId and bootVolumeId are mutually exclusive")
	case in.SSHPublicKey != "" && in.RootPassword != "":
		return fmt.Errorf("create instance: sshPublicKey and rootPassword are mutually exclusive")
	case in.GenerateRootPassword && (in.SSHPublicKey != "" || in.RootPassword != ""):
		return fmt.Errorf("create instance: generateRootPassword conflicts with sshPublicKey/rootPassword")
	}
	return nil
}

// Instance 查询单个实例（含 IP 补全）。
func (s *OciConfigService) Instance(ctx context.Context, id uint, region, instanceID string) (oci.Instance, error) {
	cred, err := s.credentialsByID(ctx, id)
	if err != nil {
		return oci.Instance{}, err
	}
	return s.client.GetInstance(ctx, cred, region, instanceID)
}

// UpdateInstance 更新实例名称、shape 或 Flex 规格。
func (s *OciConfigService) UpdateInstance(ctx context.Context, id uint, instanceID string, in oci.UpdateInstanceInput) (oci.Instance, error) {
	if in.DisplayName == "" && in.Shape == "" && in.Ocpus == 0 {
		return oci.Instance{}, fmt.Errorf("update instance: nothing to update")
	}
	cred, err := s.credentialsByID(ctx, id)
	if err != nil {
		return oci.Instance{}, err
	}
	return s.client.UpdateInstance(ctx, cred, instanceID, in)
}

// TerminateInstance 终止实例；preserveBootVolume 为 true 时保留引导卷。
func (s *OciConfigService) TerminateInstance(ctx context.Context, id uint, region, instanceID string, preserveBootVolume bool) error {
	cred, err := s.credentialsByID(ctx, id)
	if err != nil {
		return err
	}
	return s.client.TerminateInstance(ctx, cred, region, instanceID, preserveBootVolume)
}

// InstanceAction 执行电源操作：START/STOP/RESET/SOFTSTOP/SOFTRESET。
func (s *OciConfigService) InstanceAction(ctx context.Context, id uint, region, instanceID, action string) (oci.Instance, error) {
	cred, err := s.credentialsByID(ctx, id)
	if err != nil {
		return oci.Instance{}, err
	}
	return s.client.InstanceAction(ctx, cred, region, instanceID, action)
}

// BootVolumes 列出引导卷；compartmentID 为空表示租户根。
func (s *OciConfigService) BootVolumes(ctx context.Context, id uint, region, availabilityDomain, compartmentID string) ([]oci.BootVolume, error) {
	cred, err := s.credentialsInCompartment(ctx, id, compartmentID)
	if err != nil {
		return nil, err
	}
	return s.client.ListBootVolumes(ctx, cred, region, availabilityDomain)
}

// BootVolume 查询引导卷详情（含挂载实例）。
func (s *OciConfigService) BootVolume(ctx context.Context, id uint, region, bootVolumeID string) (oci.BootVolume, error) {
	cred, err := s.credentialsByID(ctx, id)
	if err != nil {
		return oci.BootVolume{}, err
	}
	return s.client.GetBootVolume(ctx, cred, region, bootVolumeID)
}

// UpdateBootVolume 更新引导卷名称、容量或性能。
func (s *OciConfigService) UpdateBootVolume(ctx context.Context, id uint, bootVolumeID string, in oci.UpdateBootVolumeInput) (oci.BootVolume, error) {
	if in.DisplayName == "" && in.SizeInGBs == 0 && in.VpusPerGB == 0 {
		return oci.BootVolume{}, fmt.Errorf("update boot volume: nothing to update")
	}
	cred, err := s.credentialsByID(ctx, id)
	if err != nil {
		return oci.BootVolume{}, err
	}
	return s.client.UpdateBootVolume(ctx, cred, bootVolumeID, in)
}

// DeleteBootVolume 删除引导卷。
func (s *OciConfigService) DeleteBootVolume(ctx context.Context, id uint, region, bootVolumeID string) error {
	cred, err := s.credentialsByID(ctx, id)
	if err != nil {
		return err
	}
	return s.client.DeleteBootVolume(ctx, cred, region, bootVolumeID)
}
