package oci

import (
	"context"
	"encoding/base64"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/oracle/oci-go-sdk/v65/core"
	"github.com/oracle/oci-go-sdk/v65/identity"
)

// Instance 是一个计算实例；IP 字段来自 VNIC 补全查询。
type Instance struct {
	ID                 string                    `json:"id"`
	DisplayName        string                    `json:"displayName"`
	LifecycleState     string                    `json:"lifecycleState"`
	Shape              string                    `json:"shape"`
	Ocpus              float32                   `json:"ocpus"`
	MemoryInGBs        float32                   `json:"memoryInGBs"`
	AvailabilityDomain string                    `json:"availabilityDomain"`
	Region             string                    `json:"region"`
	CompartmentID      string                    `json:"compartmentId"`
	ImageID            string                    `json:"imageId"`
	SubnetID           string                    `json:"subnetId,omitempty"`
	PrivateIP          string                    `json:"privateIp,omitempty"`
	PublicIP           string                    `json:"publicIp,omitempty"`
	Ipv6Addresses      []string                  `json:"ipv6Addresses,omitempty"`
	FreeformTags       map[string]string         `json:"freeformTags,omitempty"`
	DefinedTags        map[string]map[string]any `json:"definedTags,omitempty"`
	TimeCreated        *time.Time                `json:"timeCreated"`
}

// CreateInstanceInput 是创建实例的输入：镜像与引导卷二选一作为启动源，
// Flex shape 必须提供 ocpus 与 memoryInGBs。
type CreateInstanceInput struct {
	Region               string
	CompartmentID        string // 目标 compartment，空为租户根
	AvailabilityDomain   string
	DisplayName          string
	Shape                string
	Ocpus                float32
	MemoryInGBs          float32
	ImageID              string
	BootVolumeID         string
	BootVolumeSizeGBs    int64
	BootVolumeVpusPerGB  int64  // 引导卷性能，10 均衡 / 20 高性能，仅镜像启动源生效
	SubnetID             string // 为空时自动创建 VCN 与子网
	AssignPublicIP       bool
	AssignIpv6           bool
	SSHPublicKey         string            // 与 RootPassword 互斥
	RootPassword         string            // 与 SSHPublicKey 互斥，非空时生成开启 root 密码登录的 cloud-init
	GenerateRootPassword bool              // 由服务端生成随机 root 密码并写入 FreeformTags["RootPassword"]
	UserData             string            // 显式 cloud-init，base64 编码；RootPassword 非空时被其生成脚本覆盖
	FreeformTags         map[string]string // 实例自由标签
}

// UpdateInstanceInput 是更新实例的输入；零值字段不更新。
type UpdateInstanceInput struct {
	Region      string
	DisplayName string
	Shape       string // 更换 shape；运行中实例 OCI 会自动重启，换 Flex 时须同时给 ocpus
	Ocpus       float32
	MemoryInGBs float32
}

// instanceActions 是允许的电源操作集合。
var instanceActions = map[string]core.InstanceActionActionEnum{
	"START":     core.InstanceActionActionStart,
	"STOP":      core.InstanceActionActionStop,
	"RESET":     core.InstanceActionActionReset,
	"SOFTSTOP":  core.InstanceActionActionSoftstop,
	"SOFTRESET": core.InstanceActionActionSoftreset,
}

func (c *RealClient) computeClient(cred Credentials, region string) (core.ComputeClient, error) {
	cc, err := core.NewComputeClientWithConfigurationProvider(provider(cred))
	if err != nil {
		return cc, fmt.Errorf("new compute client: %w", err)
	}
	applyProxy(&cc.BaseClient, cred)
	if region != "" {
		cc.SetRegion(normalizeRegion(region))
	}
	return cc, nil
}

// ListAvailabilityDomains 实现 Client：列出区域内的可用域名称。
func (c *RealClient) ListAvailabilityDomains(ctx context.Context, cred Credentials, region string) ([]string, error) {
	ic, err := identity.NewIdentityClientWithConfigurationProvider(provider(cred))
	if err != nil {
		return nil, fmt.Errorf("new identity client: %w", err)
	}
	applyProxy(&ic.BaseClient, cred)
	if region != "" {
		ic.SetRegion(normalizeRegion(region))
	}
	resp, err := ic.ListAvailabilityDomains(ctx, identity.ListAvailabilityDomainsRequest{
		CompartmentId: &cred.TenancyOCID,
	})
	if err != nil {
		return nil, fmt.Errorf("list availability domains: %w", err)
	}
	names := make([]string, 0, len(resp.Items))
	for _, ad := range resp.Items {
		names = append(names, deref(ad.Name))
	}
	return names, nil
}

// defaultAvailabilityDomain 返回区域内第一个可用域（即 AD-1）。
func (c *RealClient) defaultAvailabilityDomain(ctx context.Context, cred Credentials, region string) (string, error) {
	ads, err := c.ListAvailabilityDomains(ctx, cred, region)
	if err != nil {
		return "", fmt.Errorf("resolve default availability domain: %w", err)
	}
	if len(ads) == 0 {
		return "", fmt.Errorf("resolve default availability domain: region has no availability domain")
	}
	return ads[0], nil
}

// ListInstances 实现 Client：列出实例并补全网络 IP 信息。
func (c *RealClient) ListInstances(ctx context.Context, cred Credentials, region string) ([]Instance, error) {
	cc, err := c.computeClient(cred, region)
	if err != nil {
		return nil, err
	}
	var instances []Instance
	var page *string
	for {
		resp, err := cc.ListInstances(ctx, core.ListInstancesRequest{
			CompartmentId: cred.EffectiveCompartment(),
			Page:          page,
		})
		if err != nil {
			return nil, fmt.Errorf("list instances: %w", err)
		}
		for _, item := range resp.Items {
			// 已终止实例的 VNIC 已不存在，保留在列表中仅供展示
			instances = append(instances, toInstance(item))
		}
		if resp.OpcNextPage == nil {
			break
		}
		page = resp.OpcNextPage
	}
	c.attachInstanceIPs(ctx, cred, region, instances)
	// 列表瘦身：标签体积大且列表页不消费（详情接口仍完整返回）
	for i := range instances {
		instances[i].FreeformTags = nil
		instances[i].DefinedTags = nil
	}
	return orEmpty(instances), nil
}

// LaunchInstance 实现 Client；availabilityDomain 为空时默认使用第一个
// 可用域（ad-1），subnetId 为空时自动准备默认网络。
func (c *RealClient) LaunchInstance(ctx context.Context, cred Credentials, in CreateInstanceInput) (Instance, error) {
	cc, err := c.computeClient(cred, in.Region)
	if err != nil {
		return Instance{}, err
	}
	if in.AvailabilityDomain == "" {
		ad, err := c.defaultAvailabilityDomain(ctx, cred, in.Region)
		if err != nil {
			return Instance{}, err
		}
		in.AvailabilityDomain = ad
	}
	if in.SubnetID == "" {
		subnetID, err := c.ensureDefaultSubnet(ctx, cred, in.Region)
		if err != nil {
			return Instance{}, fmt.Errorf("auto provision subnet: %w", err)
		}
		in.SubnetID = subnetID
	}
	details, err := buildLaunchDetails(*cred.EffectiveCompartment(), in)
	if err != nil {
		return Instance{}, err
	}
	resp, err := cc.LaunchInstance(ctx, core.LaunchInstanceRequest{LaunchInstanceDetails: details})
	if err != nil {
		return Instance{}, fmt.Errorf("launch instance: %w", err)
	}
	return toInstance(resp.Instance), nil
}

func buildLaunchDetails(compartmentID string, in CreateInstanceInput) (core.LaunchInstanceDetails, error) {
	details := core.LaunchInstanceDetails{
		CompartmentId:      &compartmentID,
		AvailabilityDomain: &in.AvailabilityDomain,
		Shape:              &in.Shape,
		DisplayName:        &in.DisplayName,
		SourceDetails:      buildInstanceSource(in),
		CreateVnicDetails: &core.CreateVnicDetails{
			SubnetId:       &in.SubnetID,
			AssignPublicIp: &in.AssignPublicIP,
		},
	}
	if details.SourceDetails == nil {
		return details, fmt.Errorf("launch instance: imageId or bootVolumeId is required")
	}
	if in.AssignIpv6 {
		details.CreateVnicDetails.AssignIpv6Ip = &in.AssignIpv6
	}
	if in.Ocpus > 0 {
		details.ShapeConfig = &core.LaunchInstanceShapeConfigDetails{Ocpus: &in.Ocpus}
		if in.MemoryInGBs > 0 {
			details.ShapeConfig.MemoryInGBs = &in.MemoryInGBs
		}
	}
	if len(in.FreeformTags) > 0 {
		details.FreeformTags = in.FreeformTags
	}
	details.Metadata = buildInstanceMetadata(in)
	return details, nil
}

func buildInstanceSource(in CreateInstanceInput) core.InstanceSourceDetails {
	if in.BootVolumeID != "" {
		return core.InstanceSourceViaBootVolumeDetails{BootVolumeId: &in.BootVolumeID}
	}
	if in.ImageID != "" {
		src := core.InstanceSourceViaImageDetails{ImageId: &in.ImageID}
		if in.BootVolumeSizeGBs > 0 {
			src.BootVolumeSizeInGBs = &in.BootVolumeSizeGBs
		}
		if in.BootVolumeVpusPerGB > 0 {
			src.BootVolumeVpusPerGB = &in.BootVolumeVpusPerGB
		}
		return src
	}
	return nil
}

func buildInstanceMetadata(in CreateInstanceInput) map[string]string {
	metadata := make(map[string]string, 2)
	if in.SSHPublicKey != "" {
		metadata["ssh_authorized_keys"] = in.SSHPublicKey
	}
	// RootPassword 生成的脚本优先作为 user_data，其次才是显式 UserData
	if in.RootPassword != "" {
		metadata["user_data"] = rootPasswordUserData(in.RootPassword)
	} else if in.UserData != "" {
		metadata["user_data"] = in.UserData
	}
	if len(metadata) == 0 {
		return nil
	}
	return metadata
}

// rootPasswordUserData 生成开启 root 密码登录的 cloud-init 脚本并 base64 编码。
// 密码用单引号安全转义，避免 shell 注入。
func rootPasswordUserData(password string) string {
	script := "#!/bin/bash\n" +
		"echo root:" + shellSingleQuote(password) + " | chpasswd\n" +
		"sed -i 's/^#\\?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config\n" +
		"sed -i 's/^#\\?PasswordAuthentication.*/PasswordAuthentication yes/g' /etc/ssh/sshd_config\n" +
		"rm -f /etc/ssh/sshd_config.d/* /etc/ssh/ssh_config.d/*\n" +
		"systemctl restart sshd\n"
	return base64.StdEncoding.EncodeToString([]byte(script))
}

// shellSingleQuote 用单引号包裹字符串，内部单引号转义为 '\”。
func shellSingleQuote(s string) string {
	return "'" + strings.ReplaceAll(s, "'", `'\''`) + "'"
}

// GetInstance 实现 Client：查询实例并补全网络 IP 信息。
func (c *RealClient) GetInstance(ctx context.Context, cred Credentials, region, instanceID string) (Instance, error) {
	cc, err := c.computeClient(cred, region)
	if err != nil {
		return Instance{}, err
	}
	resp, err := cc.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &instanceID})
	if err != nil {
		return Instance{}, fmt.Errorf("get instance %s: %w", instanceID, err)
	}
	instances := []Instance{toInstance(resp.Instance)}
	c.attachInstanceIPs(ctx, cred, region, instances)
	return instances[0], nil
}

// UpdateInstance 实现 Client：改名或调整 Flex 规格。
func (c *RealClient) UpdateInstance(ctx context.Context, cred Credentials, instanceID string, in UpdateInstanceInput) (Instance, error) {
	cc, err := c.computeClient(cred, in.Region)
	if err != nil {
		return Instance{}, err
	}
	details := core.UpdateInstanceDetails{}
	if in.DisplayName != "" {
		details.DisplayName = &in.DisplayName
	}
	if in.Shape != "" {
		details.Shape = &in.Shape
	}
	if in.Ocpus > 0 {
		details.ShapeConfig = &core.UpdateInstanceShapeConfigDetails{Ocpus: &in.Ocpus}
		if in.MemoryInGBs > 0 {
			details.ShapeConfig.MemoryInGBs = &in.MemoryInGBs
		}
	}
	resp, err := cc.UpdateInstance(ctx, core.UpdateInstanceRequest{
		InstanceId:            &instanceID,
		UpdateInstanceDetails: details,
	})
	if err != nil {
		return Instance{}, fmt.Errorf("update instance %s: %w", instanceID, err)
	}
	return toInstance(resp.Instance), nil
}

// TerminateInstance 实现 Client；preserveBootVolume 为 true 时保留引导卷。
func (c *RealClient) TerminateInstance(ctx context.Context, cred Credentials, region, instanceID string, preserveBootVolume bool) error {
	cc, err := c.computeClient(cred, region)
	if err != nil {
		return err
	}
	_, err = cc.TerminateInstance(ctx, core.TerminateInstanceRequest{
		InstanceId:         &instanceID,
		PreserveBootVolume: &preserveBootVolume,
	})
	if err != nil {
		return fmt.Errorf("terminate instance %s: %w", instanceID, err)
	}
	return nil
}

// InstanceAction 实现 Client：执行电源操作。
func (c *RealClient) InstanceAction(ctx context.Context, cred Credentials, region, instanceID, action string) (Instance, error) {
	actionEnum, ok := instanceActions[strings.ToUpper(strings.TrimSpace(action))]
	if !ok {
		return Instance{}, fmt.Errorf("instance action: unsupported action %q", action)
	}
	cc, err := c.computeClient(cred, region)
	if err != nil {
		return Instance{}, err
	}
	resp, err := cc.InstanceAction(ctx, core.InstanceActionRequest{
		InstanceId: &instanceID,
		Action:     actionEnum,
	})
	if err != nil {
		return Instance{}, fmt.Errorf("instance action %s on %s: %w", action, instanceID, err)
	}
	return toInstance(resp.Instance), nil
}

// hostCompartment 返回宿主资源自身所在 compartment 的请求指针：OCI 的
// 关联查询要求 compartmentId 与资源实际所在 compartment 一致，传租户根
// 会漏掉子 compartment 里的资源；id 缺失时保底用生效 compartment。
func hostCompartment(cred Credentials, id string) *string {
	if id != "" {
		return &id
	}
	return cred.EffectiveCompartment()
}

// attachInstanceIPs 批量补全实例的子网与 IP：按实例所在 compartment 分组
// 列出 VNIC 挂载，再逐 VNIC 查询地址；直接用实例自带的 compartment，
// 避免为定位挂载再逐台调用 GetInstance。
func (c *RealClient) attachInstanceIPs(ctx context.Context, cred Credentials, region string, instances []Instance) {
	active := make(map[string]*Instance, len(instances))
	compartments := make(map[string]struct{})
	for i := range instances {
		if instances[i].LifecycleState == string(core.InstanceLifecycleStateTerminated) {
			continue
		}
		active[instances[i].ID] = &instances[i]
		compartments[deref(hostCompartment(cred, instances[i].CompartmentID))] = struct{}{}
	}
	if len(active) == 0 {
		return
	}
	cc, err := c.computeClient(cred, region)
	if err != nil {
		return
	}
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return
	}
	for compartmentID := range compartments {
		fillInstanceIPs(ctx, cc, vn, compartmentID, active)
	}
}

// fillInstanceIPs 列出单个 compartment 内的 VNIC 挂载，为命中的实例补全地址。
// 逐 VNIC 的 GetVnic 是列表接口的主要耗时点，限并发 8 路并行拉取。
func fillInstanceIPs(ctx context.Context, cc core.ComputeClient, vn core.VirtualNetworkClient, compartmentID string, active map[string]*Instance) {
	attResp, err := cc.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{CompartmentId: &compartmentID})
	if err != nil {
		return
	}
	var (
		wg          sync.WaitGroup
		mu          sync.Mutex
		sem         = make(chan struct{}, 8)
		primarySeen = make(map[*Instance]bool)
	)
	for _, att := range attResp.Items {
		inst, ok := active[deref(att.InstanceId)]
		if !ok || att.VnicId == nil {
			continue
		}
		wg.Add(1)
		sem <- struct{}{}
		go func(vnicID *string, inst *Instance) {
			defer wg.Done()
			defer func() { <-sem }()
			vnicResp, err := vn.GetVnic(ctx, core.GetVnicRequest{VnicId: vnicID})
			if err != nil {
				return
			}
			mu.Lock()
			applyVnicAddrs(inst, vnicResp.Vnic, primarySeen)
			mu.Unlock()
		}(att.VnicId, inst)
	}
	wg.Wait()
}

// applyVnicAddrs 将 VNIC 地址写入实例。多网卡实例以主网卡为准：
// 主网卡返回前先用先到的网卡兜底，主网卡到达后覆盖并锁定，避免并发
// 完成顺序决定展示结果。调用方需持有保护 inst 与 primarySeen 的锁。
func applyVnicAddrs(inst *Instance, v core.Vnic, primarySeen map[*Instance]bool) {
	isPrimary := v.IsPrimary != nil && *v.IsPrimary
	if !isPrimary && (primarySeen[inst] || inst.SubnetID != "") {
		return
	}
	inst.SubnetID = deref(v.SubnetId)
	inst.PrivateIP = deref(v.PrivateIp)
	inst.PublicIP = deref(v.PublicIp)
	inst.Ipv6Addresses = v.Ipv6Addresses
	if isPrimary {
		primarySeen[inst] = true
	}
}

func toInstance(inst core.Instance) Instance {
	out := Instance{
		ID:                 deref(inst.Id),
		DisplayName:        deref(inst.DisplayName),
		LifecycleState:     string(inst.LifecycleState),
		Shape:              deref(inst.Shape),
		AvailabilityDomain: deref(inst.AvailabilityDomain),
		Region:             deref(inst.Region),
		CompartmentID:      deref(inst.CompartmentId),
		ImageID:            deref(inst.ImageId),
		FreeformTags:       inst.FreeformTags,
		DefinedTags:        inst.DefinedTags,
		TimeCreated:        sdkTime(inst.TimeCreated),
	}
	if inst.ShapeConfig != nil {
		if inst.ShapeConfig.Ocpus != nil {
			out.Ocpus = *inst.ShapeConfig.Ocpus
		}
		if inst.ShapeConfig.MemoryInGBs != nil {
			out.MemoryInGBs = *inst.ShapeConfig.MemoryInGBs
		}
	}
	return out
}
