package oci

import (
	"context"
	"fmt"
	"time"

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

// Vnic 是实例已附加的虚拟网卡(附加关系与地址信息的合并视图)。
type Vnic struct {
	AttachmentID   string     `json:"attachmentId"`
	VnicID         string     `json:"vnicId"`
	DisplayName    string     `json:"displayName"`
	IsPrimary      bool       `json:"isPrimary"`
	PrivateIP      string     `json:"privateIp"`
	PublicIP       string     `json:"publicIp"`
	Ipv6Addresses  []string   `json:"ipv6Addresses"`
	SubnetID       string     `json:"subnetId"`
	SubnetName     string     `json:"subnetName,omitempty"`
	LifecycleState string     `json:"lifecycleState"`
	TimeCreated    *time.Time `json:"timeCreated"`
}

// AttachVnicInput 是附加新 VNIC 的输入;PrivateIP 留空自动分配。
type AttachVnicInput struct {
	SubnetID       string `json:"subnetId" binding:"required"`
	DisplayName    string `json:"displayName"`
	PrivateIP      string `json:"privateIp"`
	AssignPublicIP bool   `json:"assignPublicIp"`
	// AssignIpv6 为 true 时同时自动分配一个 IPv6(要求子网已启用 IPv6)。
	AssignIpv6 bool `json:"assignIpv6"`
}

// ListInstanceVnics 实现 Client:列出实例全部 VNIC(含附加中/分离中的关系)。
func (c *RealClient) ListInstanceVnics(ctx context.Context, cred Credentials, region, instanceID string) ([]Vnic, error) {
	cc, err := c.computeClient(cred, region)
	if err != nil {
		return nil, err
	}
	instResp, err := cc.GetInstance(ctx, core.GetInstanceRequest{InstanceId: &instanceID})
	if err != nil {
		return nil, fmt.Errorf("get instance %s: %w", instanceID, err)
	}
	// 附加关系在实例所在 compartment,传租户根会漏查
	attResp, err := cc.ListVnicAttachments(ctx, core.ListVnicAttachmentsRequest{
		CompartmentId: hostCompartment(cred, deref(instResp.CompartmentId)),
		InstanceId:    &instanceID,
	})
	if err != nil {
		return nil, fmt.Errorf("list vnic attachments: %w", err)
	}
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return nil, err
	}
	out := make([]Vnic, 0, len(attResp.Items))
	subnetNames := map[string]string{}
	for _, att := range attResp.Items {
		out = append(out, buildVnic(ctx, vn, att, subnetNames))
	}
	return out, nil
}

// buildVnic 合并附加关系与 VNIC 详情;VNIC 未就绪(附加中)时只有关系字段。
func buildVnic(ctx context.Context, vn core.VirtualNetworkClient, att core.VnicAttachment, subnetNames map[string]string) Vnic {
	v := Vnic{
		AttachmentID:   deref(att.Id),
		VnicID:         deref(att.VnicId),
		DisplayName:    deref(att.DisplayName),
		SubnetID:       deref(att.SubnetId),
		LifecycleState: string(att.LifecycleState),
		TimeCreated:    sdkTime(att.TimeCreated),
	}
	if att.VnicId != nil {
		if resp, err := vn.GetVnic(ctx, core.GetVnicRequest{VnicId: att.VnicId}); err == nil {
			v.DisplayName = deref(resp.DisplayName)
			v.PrivateIP = deref(resp.PrivateIp)
			v.PublicIP = deref(resp.PublicIp)
			v.Ipv6Addresses = resp.Ipv6Addresses
			if resp.IsPrimary != nil {
				v.IsPrimary = *resp.IsPrimary
			}
			if deref(resp.SubnetId) != "" {
				v.SubnetID = deref(resp.SubnetId)
			}
		}
	}
	v.SubnetName = subnetNameOf(ctx, vn, v.SubnetID, subnetNames)
	return v
}

// subnetNameOf 查子网显示名,map 去重;失败留空不影响列表。
func subnetNameOf(ctx context.Context, vn core.VirtualNetworkClient, id string, cache map[string]string) string {
	if id == "" {
		return ""
	}
	if name, ok := cache[id]; ok {
		return name
	}
	name := ""
	if resp, err := vn.GetSubnet(ctx, core.GetSubnetRequest{SubnetId: &id}); err == nil {
		name = deref(resp.DisplayName)
	}
	cache[id] = name
	return name
}

// AttachVnic 实现 Client:为实例附加次要 VNIC;实例须运行中且 shape 有空余 VNIC 配额。
func (c *RealClient) AttachVnic(ctx context.Context, cred Credentials, region, instanceID string, in AttachVnicInput) (Vnic, error) {
	cc, err := c.computeClient(cred, region)
	if err != nil {
		return Vnic{}, err
	}
	details := &core.CreateVnicDetails{
		SubnetId:       &in.SubnetID,
		AssignPublicIp: &in.AssignPublicIP,
	}
	if in.AssignIpv6 {
		details.AssignIpv6Ip = &in.AssignIpv6
	}
	if in.DisplayName != "" {
		details.DisplayName = &in.DisplayName
	}
	if in.PrivateIP != "" {
		details.PrivateIp = &in.PrivateIP
	}
	resp, err := cc.AttachVnic(ctx, core.AttachVnicRequest{
		AttachVnicDetails: core.AttachVnicDetails{InstanceId: &instanceID, CreateVnicDetails: details},
	})
	if err != nil {
		return Vnic{}, fmt.Errorf("attach vnic: %w", err)
	}
	att := resp.VnicAttachment
	return Vnic{
		AttachmentID:   deref(att.Id),
		VnicID:         deref(att.VnicId),
		DisplayName:    deref(att.DisplayName),
		SubnetID:       deref(att.SubnetId),
		LifecycleState: string(att.LifecycleState),
		TimeCreated:    sdkTime(att.TimeCreated),
	}, nil
}

// DetachVnic 实现 Client:分离次要 VNIC(主 VNIC 由 OCI 拒绝)。
func (c *RealClient) DetachVnic(ctx context.Context, cred Credentials, region, attachmentID string) error {
	cc, err := c.computeClient(cred, region)
	if err != nil {
		return err
	}
	if _, err := cc.DetachVnic(ctx, core.DetachVnicRequest{VnicAttachmentId: &attachmentID}); err != nil {
		return fmt.Errorf("detach vnic %s: %w", attachmentID, err)
	}
	return nil
}
