package oci

import (
	"context"
	"fmt"

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

// instanceVnics 列出实例的全部 VNIC，并一并返回实例所在 compartment，
// 供调用方复用为后续按实例定位查询的 compartmentId，避免重复 GetInstance。
func (c *RealClient) instanceVnics(ctx context.Context, cred Credentials, region, instanceID string) ([]core.Vnic, string, error) {
	cc, err := c.computeClient(cred, region)
	if err != nil {
		return nil, "", err
	}
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return nil, "", err
	}
	// VNIC 挂载记录在实例所在 compartment，先取实例定位，传租户根会漏查
	instResp, err := cc.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &instanceID})
	if err != nil {
		return nil, "", fmt.Errorf("get instance %s: %w", instanceID, err)
	}
	compartmentID := hostCompartment(cred, deref(instResp.CompartmentId))
	resp, err := cc.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{
		CompartmentId: compartmentID,
		InstanceId:    &instanceID,
	})
	if err != nil {
		return nil, "", fmt.Errorf("list vnic attachments: %w", err)
	}
	return attachedVnics(ctx, vn, resp.Items), *compartmentID, nil
}

// attachedVnics 逐挂载记录查询 VNIC 详情，跳过查询失败的记录。
func attachedVnics(ctx context.Context, vn core.VirtualNetworkClient, atts []core.VnicAttachment) []core.Vnic {
	vnics := make([]core.Vnic, 0, len(atts))
	for _, att := range atts {
		if att.VnicId == nil {
			continue
		}
		vnicResp, err := vn.GetVnic(ctx, core.GetVnicRequest{VnicId: att.VnicId})
		if err != nil {
			continue
		}
		vnics = append(vnics, vnicResp.Vnic)
	}
	return vnics
}

// lookupPublicIP 查询私有 IP 绑定的公网 IP，未绑定返回 nil。
func lookupPublicIP(ctx context.Context, vn core.VirtualNetworkClient, privateIPID *string) *core.PublicIp {
	resp, err := vn.GetPublicIpByPrivateIpId(ctx, core.GetPublicIpByPrivateIpIdRequest{
		GetPublicIpByPrivateIpIdDetails: core.GetPublicIpByPrivateIpIdDetails{PrivateIpId: privateIPID},
	})
	if err != nil || resp.Id == nil {
		return nil
	}
	return &resp.PublicIp
}

// ChangeInstancePublicIP 实现 Client：为实例主 VNIC 主私有 IP 更换临时公网 IP。
// 删除旧的临时公网 IP 再分配新的；若旧地址是保留 IP（RESERVED），拒绝操作。
func (c *RealClient) ChangeInstancePublicIP(ctx context.Context, cred Credentials, region, instanceID string) (string, error) {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return "", err
	}
	priv, err := c.primaryPrivateIP(ctx, cred, region, instanceID)
	if err != nil {
		return "", err
	}
	if existing := lookupPublicIP(ctx, vn, priv.Id); existing != nil {
		if existing.Lifetime == core.PublicIpLifetimeReserved {
			return "", fmt.Errorf("change public ip: private ip has a reserved public ip, release it manually")
		}
		if _, err := vn.DeletePublicIp(ctx, core.DeletePublicIpRequest{PublicIpId: existing.Id}); err != nil {
			return "", fmt.Errorf("delete old public ip: %w", err)
		}
	}
	// OCI 要求临时公网 IP 与其私有 IP 同 compartment，故不能用生效 compartment
	resp, err := vn.CreatePublicIp(ctx, core.CreatePublicIpRequest{
		CreatePublicIpDetails: core.CreatePublicIpDetails{
			CompartmentId: hostCompartment(cred, deref(priv.CompartmentId)),
			Lifetime:      core.CreatePublicIpDetailsLifetimeEphemeral,
			PrivateIpId:   priv.Id,
		},
	})
	if err != nil {
		return "", fmt.Errorf("create public ip: %w", err)
	}
	return deref(resp.IpAddress), nil
}

// primaryVnic 返回实例的主 VNIC。
func (c *RealClient) primaryVnic(ctx context.Context, cred Credentials, region, instanceID string) (core.Vnic, error) {
	vnics, _, err := c.instanceVnics(ctx, cred, region, instanceID)
	if err != nil {
		return core.Vnic{}, err
	}
	for i := range vnics {
		if vnics[i].IsPrimary != nil && *vnics[i].IsPrimary {
			return vnics[i], nil
		}
	}
	return core.Vnic{}, fmt.Errorf("instance %s has no primary vnic", instanceID)
}

// primaryPrivateIP 返回实例主 VNIC 上的主私有 IP 对象；
// 保留完整对象是为了让调用方能拿到私有 IP 所在的 compartment。
func (c *RealClient) primaryPrivateIP(ctx context.Context, cred Credentials, region, instanceID string) (core.PrivateIp, error) {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return core.PrivateIp{}, err
	}
	vnic, err := c.primaryVnic(ctx, cred, region, instanceID)
	if err != nil {
		return core.PrivateIp{}, err
	}
	privResp, err := vn.ListPrivateIps(ctx, core.ListPrivateIpsRequest{VnicId: vnic.Id})
	if err != nil {
		return core.PrivateIp{}, fmt.Errorf("list private ips: %w", err)
	}
	for _, priv := range privResp.Items {
		if priv.IsPrimary != nil && *priv.IsPrimary {
			return priv, nil
		}
	}
	return core.PrivateIp{}, fmt.Errorf("change public ip: primary vnic has no primary private ip")
}
