package oci

import (
	"context"
	"fmt"
	"time"

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

// VCN 是一个虚拟云网络。
type VCN struct {
	ID                    string     `json:"id"`
	DisplayName           string     `json:"displayName"`
	LifecycleState        string     `json:"lifecycleState"`
	CompartmentID         string     `json:"compartmentId"`
	CidrBlocks            []string   `json:"cidrBlocks"`
	Ipv6CidrBlocks        []string   `json:"ipv6CidrBlocks"`
	DnsLabel              string     `json:"dnsLabel"`
	DefaultRouteTableID   string     `json:"defaultRouteTableId"`
	DefaultSecurityListID string     `json:"defaultSecurityListId"`
	TimeCreated           *time.Time `json:"timeCreated"`
}

// CreateVCNInput 是创建 VCN 的输入。
type CreateVCNInput struct {
	Region        string
	CompartmentID string // 目标 compartment，空为租户根
	DisplayName   string
	CidrBlock     string
	DnsLabel      string
	EnableIPv6    bool // 创建时同时分配 Oracle GUA IPv6 /56
}

// Subnet 是 VCN 下的一个子网。
type Subnet struct {
	ID                 string     `json:"id"`
	VcnID              string     `json:"vcnId"`
	DisplayName        string     `json:"displayName"`
	LifecycleState     string     `json:"lifecycleState"`
	CidrBlock          string     `json:"cidrBlock"`
	Ipv6CidrBlock      string     `json:"ipv6CidrBlock"`
	DnsLabel           string     `json:"dnsLabel"`
	ProhibitPublicIP   bool       `json:"prohibitPublicIp"`
	AvailabilityDomain string     `json:"availabilityDomain"`
	RouteTableID       string     `json:"routeTableId"`
	TimeCreated        *time.Time `json:"timeCreated"`
}

// CreateSubnetInput 是创建子网的输入。
type CreateSubnetInput struct {
	Region           string
	VcnID            string
	DisplayName      string
	CidrBlock        string
	DnsLabel         string
	Ipv6CidrBlock    string
	ProhibitPublicIP bool
}

// SecurityRule 是安全列表中的一条规则；TCP/UDP 只支持目标端口范围。
type SecurityRule struct {
	Protocol    string `json:"protocol"` // all、6(TCP)、17(UDP)、1(ICMP)、58(ICMPv6)
	Source      string `json:"source,omitempty"`
	Destination string `json:"destination,omitempty"`
	IsStateless bool   `json:"isStateless"`
	PortMin     *int   `json:"portMin,omitempty"`
	PortMax     *int   `json:"portMax,omitempty"`
	IcmpType    *int   `json:"icmpType,omitempty"`
	IcmpCode    *int   `json:"icmpCode,omitempty"`
	Description string `json:"description,omitempty"`
}

// SecurityList 是一个安全列表及其规则。
type SecurityList struct {
	ID             string         `json:"id"`
	VcnID          string         `json:"vcnId"`
	DisplayName    string         `json:"displayName"`
	LifecycleState string         `json:"lifecycleState"`
	IngressRules   []SecurityRule `json:"ingressRules"`
	EgressRules    []SecurityRule `json:"egressRules"`
	TimeCreated    *time.Time     `json:"timeCreated"`
}

// CreateSecurityListInput 是创建安全列表的输入。
type CreateSecurityListInput struct {
	Region       string
	VcnID        string
	DisplayName  string
	IngressRules []SecurityRule
	EgressRules  []SecurityRule
}

// UpdateSecurityListInput 是更新安全列表的输入；nil 字段保持不变，
// 规则字段一经提供即整体替换。
type UpdateSecurityListInput struct {
	Region       string
	DisplayName  string
	IngressRules *[]SecurityRule
	EgressRules  *[]SecurityRule
}

// 自动建网使用的固定名称，便于复用而非每次新建 VCN。
const (
	autoVcnName    = "oci-portal-auto-vcn"
	autoSubnetName = "oci-portal-auto-subnet"
	autoVcnCidr    = "10.0.0.0/16"
	autoSubnetCidr = "10.0.0.0/24"
)

// ensureDefaultSubnet 在 subnetId 为空时准备一个可上公网的子网：
// 复用已有的 oci-portal-auto-vcn，否则新建 VCN + IGW + 默认路由 + 子网。
func (c *RealClient) ensureDefaultSubnet(ctx context.Context, cred Credentials, region string) (string, error) {
	vcns, err := c.ListVCNs(ctx, cred, region)
	if err != nil {
		return "", err
	}
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return "", err
	}
	for _, v := range vcns {
		if v.DisplayName != autoVcnName {
			continue
		}
		// 列表项自带 VCN 所在 compartment，直接用它列子网，免去重复 GetVCN
		subnets, err := listSubnets(ctx, vn, *hostCompartment(cred, v.CompartmentID), v.ID)
		if err == nil && len(subnets) > 0 {
			return subnets[0].ID, nil
		}
	}
	return c.createDefaultNetwork(ctx, cred, region)
}

func (c *RealClient) createDefaultNetwork(ctx context.Context, cred Credentials, region string) (string, error) {
	vcn, err := c.CreateVCN(ctx, cred, CreateVCNInput{
		Region: region, DisplayName: autoVcnName, CidrBlock: autoVcnCidr, DnsLabel: "ociportalauto",
		EnableIPv6: true,
	})
	if err != nil {
		return "", err
	}
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return "", err
	}
	// IGW 与子网都建在新 VCN 自己的 compartment，与其余关联资源保持同域
	vcnCompartment := *hostCompartment(cred, vcn.CompartmentID)
	igwID, err := ensureInternetGateway(ctx, vn, vcnCompartment, vcn.ID)
	if err != nil {
		return "", err
	}
	if err := addInternetRoute(ctx, vn, vcn.DefaultRouteTableID, igwID, "0.0.0.0/0"); err != nil {
		return "", err
	}
	subnet, err := c.createSubnetIn(ctx, cred, vcnCompartment, CreateSubnetInput{
		Region: region, VcnID: vcn.ID, DisplayName: autoSubnetName, CidrBlock: autoSubnetCidr, DnsLabel: "ociauto",
		Ipv6CidrBlock: autoSubnetIPv6Cidr(ctx, vn, vcn.ID, vcn.DefaultRouteTableID, igwID),
	})
	if err != nil {
		return "", err
	}
	return subnet.ID, nil
}

// autoSubnetIPv6Cidr 等待自动 VCN 的 IPv6 /56 分配就绪并补 ::/0 路由，
// 返回子网可用的 /64；IPv6 尽力而为，失败返回空串降级纯 IPv4 不阻塞建网。
func autoSubnetIPv6Cidr(ctx context.Context, vn core.VirtualNetworkClient, vcnID, routeTableID, igwID string) string {
	vcnCidr, err := waitVCNIPv6Cidr(ctx, vn, vcnID)
	if err != nil {
		return ""
	}
	if err := addInternetRoute(ctx, vn, routeTableID, igwID, ipv6AnyCIDR); err != nil {
		return ""
	}
	cidr, err := nextSubnetIPv6CIDR(vcnCidr, nil)
	if err != nil {
		return ""
	}
	return cidr
}

// addInternetRoute 在路由表补 dest → IGW，已存在则跳过。
func addInternetRoute(ctx context.Context, vn core.VirtualNetworkClient, routeTableID, igwID, dest string) error {
	resp, err := vn.GetRouteTable(ctx, core.GetRouteTableRequest{RtId: &routeTableID})
	if err != nil {
		return fmt.Errorf("get route table: %w", err)
	}
	for _, rule := range resp.RouteRules {
		if deref(rule.Destination) == dest {
			return nil
		}
	}
	rules := append(resp.RouteRules, core.RouteRule{
		Destination:     &dest,
		DestinationType: core.RouteRuleDestinationTypeCidrBlock,
		NetworkEntityId: &igwID,
	})
	_, err = vn.UpdateRouteTable(ctx, core.UpdateRouteTableRequest{
		RtId:                    &routeTableID,
		UpdateRouteTableDetails: core.UpdateRouteTableDetails{RouteRules: rules},
	})
	if err != nil {
		return fmt.Errorf("add internet route %s: %w", dest, err)
	}
	return nil
}

func (c *RealClient) vcnClient(cred Credentials, region string) (core.VirtualNetworkClient, error) {
	vn, err := core.NewVirtualNetworkClientWithConfigurationProvider(provider(cred))
	if err != nil {
		return vn, fmt.Errorf("new virtual network client: %w", err)
	}
	applyProxy(&vn.BaseClient, cred)
	if region != "" {
		vn.SetRegion(normalizeRegion(region))
	}
	return vn, nil
}

// ListVCNs 实现 Client。
func (c *RealClient) ListVCNs(ctx context.Context, cred Credentials, region string) ([]VCN, error) {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return nil, err
	}
	var vcns []VCN
	var page *string
	for {
		resp, err := vn.ListVcns(ctx, core.ListVcnsRequest{CompartmentId: cred.EffectiveCompartment(), Page: page})
		if err != nil {
			return nil, fmt.Errorf("list vcns: %w", err)
		}
		for _, item := range resp.Items {
			vcns = append(vcns, toVCN(item))
		}
		if resp.OpcNextPage == nil {
			return orEmpty(vcns), nil
		}
		page = resp.OpcNextPage
	}
}

// CreateVCN 实现 Client；创建后将默认安全列表放开为出入方向全放行。
func (c *RealClient) CreateVCN(ctx context.Context, cred Credentials, in CreateVCNInput) (VCN, error) {
	vn, err := c.vcnClient(cred, in.Region)
	if err != nil {
		return VCN{}, err
	}
	details := core.CreateVcnDetails{
		CompartmentId: cred.EffectiveCompartment(),
		CidrBlocks:    []string{in.CidrBlock},
		DisplayName:   &in.DisplayName,
	}
	if in.DnsLabel != "" {
		details.DnsLabel = &in.DnsLabel
	}
	if in.EnableIPv6 {
		details.IsIpv6Enabled = &in.EnableIPv6
	}
	resp, err := vn.CreateVcn(ctx, core.CreateVcnRequest{CreateVcnDetails: details})
	if err != nil {
		return VCN{}, fmt.Errorf("create vcn: %w", err)
	}
	vcn := toVCN(resp.Vcn)
	// VCN 初始化会异步写入默认安全列表的初始规则，未就绪时替换会被覆盖
	if err := waitVCNAvailable(ctx, vn, vcn.ID); err != nil {
		return vcn, err
	}
	if err := openDefaultSecurityList(ctx, vn, vcn.DefaultSecurityListID, in.EnableIPv6); err != nil {
		return vcn, err
	}
	return vcn, nil
}

// waitVCNAvailable 轮询等待 VCN 进入 AVAILABLE，最长约 60 秒。
func waitVCNAvailable(ctx context.Context, vn core.VirtualNetworkClient, vcnID string) error {
	for i := 0; i < 30; i++ {
		resp, err := vn.GetVcn(ctx, core.GetVcnRequest{VcnId: &vcnID})
		if err != nil {
			return fmt.Errorf("poll vcn state: %w", err)
		}
		if resp.LifecycleState == core.VcnLifecycleStateAvailable {
			return nil
		}
		select {
		case <-ctx.Done():
			return fmt.Errorf("poll vcn state: %w", ctx.Err())
		case <-time.After(2 * time.Second):
		}
	}
	return fmt.Errorf("poll vcn state: timed out waiting for AVAILABLE")
}

// openDefaultSecurityList 将安全列表规则替换为出入方向全放行；
// includeIPv6 时附带 ::/0 规则（OCI 拒绝在未启用 IPv6 的 VCN 上写入）。
func openDefaultSecurityList(ctx context.Context, vn core.VirtualNetworkClient, securityListID string, includeIPv6 bool) error {
	protocol := "all"
	v4, v6 := "0.0.0.0/0", ipv6AnyCIDR
	ingress := []core.IngressSecurityRule{{Protocol: &protocol, Source: &v4}}
	egress := []core.EgressSecurityRule{{Protocol: &protocol, Destination: &v4}}
	if includeIPv6 {
		ingress = append(ingress, core.IngressSecurityRule{Protocol: &protocol, Source: &v6})
		egress = append(egress, core.EgressSecurityRule{Protocol: &protocol, Destination: &v6})
	}
	_, err := vn.UpdateSecurityList(ctx, core.UpdateSecurityListRequest{
		SecurityListId: &securityListID,
		UpdateSecurityListDetails: core.UpdateSecurityListDetails{
			IngressSecurityRules: ingress,
			EgressSecurityRules:  egress,
		},
	})
	if err != nil {
		return fmt.Errorf("open default security list %s: %w", securityListID, err)
	}
	return nil
}

// GetVCN 实现 Client。
func (c *RealClient) GetVCN(ctx context.Context, cred Credentials, region, vcnID string) (VCN, error) {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return VCN{}, err
	}
	resp, err := vn.GetVcn(ctx, core.GetVcnRequest{VcnId: &vcnID})
	if err != nil {
		return VCN{}, fmt.Errorf("get vcn %s: %w", vcnID, err)
	}
	return toVCN(resp.Vcn), nil
}

// UpdateVCN 实现 Client：当前仅支持改名。
func (c *RealClient) UpdateVCN(ctx context.Context, cred Credentials, region, vcnID, displayName string) (VCN, error) {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return VCN{}, err
	}
	resp, err := vn.UpdateVcn(ctx, core.UpdateVcnRequest{
		VcnId:            &vcnID,
		UpdateVcnDetails: core.UpdateVcnDetails{DisplayName: &displayName},
	})
	if err != nil {
		return VCN{}, fmt.Errorf("update vcn %s: %w", vcnID, err)
	}
	return toVCN(resp.Vcn), nil
}

// DeleteVCN 实现 Client：级联清理子网、网关与非默认路由表 / 安全列表 /
// DHCP 选项后删除 VCN（见 vcndelete.go）；子网被实例占用时 OCI 返回 409。
func (c *RealClient) DeleteVCN(ctx context.Context, cred Credentials, region, vcnID string) error {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return err
	}
	return deleteVcnCascade(ctx, vn, vcnID)
}

// vcnScopedCompartment 返回按 VCN 定位的查询与创建应使用的 compartment：
// OCI 要求 compartmentId 与资源实际所在 compartment 一致，vcnID 非空时取
// VCN 自身所在 compartment，为空（全 compartment 列表）时用生效 compartment。
func (c *RealClient) vcnScopedCompartment(ctx context.Context, cred Credentials, region, vcnID string) (string, error) {
	if vcnID == "" {
		return *cred.EffectiveCompartment(), nil
	}
	vcn, err := c.GetVCN(ctx, cred, region, vcnID)
	if err != nil {
		return "", err
	}
	return *hostCompartment(cred, vcn.CompartmentID), nil
}

// ListSubnets 实现 Client；vcnID 为空时列出全部子网。
func (c *RealClient) ListSubnets(ctx context.Context, cred Credentials, region, vcnID string) ([]Subnet, error) {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return nil, err
	}
	compartmentID, err := c.vcnScopedCompartment(ctx, cred, region, vcnID)
	if err != nil {
		return nil, err
	}
	return listSubnets(ctx, vn, compartmentID, vcnID)
}

func listSubnets(ctx context.Context, vn core.VirtualNetworkClient, compartmentID, vcnID string) ([]Subnet, error) {
	var subnets []Subnet
	var page *string
	for {
		req := core.ListSubnetsRequest{CompartmentId: &compartmentID, Page: page}
		if vcnID != "" {
			req.VcnId = &vcnID
		}
		resp, err := vn.ListSubnets(ctx, req)
		if err != nil {
			return nil, fmt.Errorf("list subnets: %w", err)
		}
		for _, item := range resp.Items {
			subnets = append(subnets, toSubnet(item))
		}
		if resp.OpcNextPage == nil {
			return orEmpty(subnets), nil
		}
		page = resp.OpcNextPage
	}
}

// CreateSubnet 实现 Client；不传 AD 时创建 regional 子网。
// 子网跟随其 VCN 所在 compartment 创建，避免跨 compartment 悬挂资源。
func (c *RealClient) CreateSubnet(ctx context.Context, cred Credentials, in CreateSubnetInput) (Subnet, error) {
	compartmentID, err := c.vcnScopedCompartment(ctx, cred, in.Region, in.VcnID)
	if err != nil {
		return Subnet{}, err
	}
	return c.createSubnetIn(ctx, cred, compartmentID, in)
}

// createSubnetIn 在指定 compartment 创建子网，供已知 VCN compartment
// 的调用方（如自动建网）复用，免去一次重复的 GetVCN。
func (c *RealClient) createSubnetIn(ctx context.Context, cred Credentials, compartmentID string, in CreateSubnetInput) (Subnet, error) {
	vn, err := c.vcnClient(cred, in.Region)
	if err != nil {
		return Subnet{}, err
	}
	resp, err := vn.CreateSubnet(ctx, core.CreateSubnetRequest{CreateSubnetDetails: buildSubnetDetails(compartmentID, in)})
	if err != nil {
		return Subnet{}, fmt.Errorf("create subnet: %w", err)
	}
	return toSubnet(resp.Subnet), nil
}

// buildSubnetDetails 组装创建子网请求体；可选字段仅在非零值时下发。
func buildSubnetDetails(compartmentID string, in CreateSubnetInput) core.CreateSubnetDetails {
	details := core.CreateSubnetDetails{
		CompartmentId: &compartmentID,
		VcnId:         &in.VcnID,
		CidrBlock:     &in.CidrBlock,
		DisplayName:   &in.DisplayName,
	}
	if in.DnsLabel != "" {
		details.DnsLabel = &in.DnsLabel
	}
	if in.Ipv6CidrBlock != "" {
		details.Ipv6CidrBlock = &in.Ipv6CidrBlock
	}
	if in.ProhibitPublicIP {
		details.ProhibitPublicIpOnVnic = &in.ProhibitPublicIP
	}
	return details
}

// GetSubnet 实现 Client。
func (c *RealClient) GetSubnet(ctx context.Context, cred Credentials, region, subnetID string) (Subnet, error) {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return Subnet{}, err
	}
	resp, err := vn.GetSubnet(ctx, core.GetSubnetRequest{SubnetId: &subnetID})
	if err != nil {
		return Subnet{}, fmt.Errorf("get subnet %s: %w", subnetID, err)
	}
	return toSubnet(resp.Subnet), nil
}

// UpdateSubnet 实现 Client：当前仅支持改名。
func (c *RealClient) UpdateSubnet(ctx context.Context, cred Credentials, region, subnetID, displayName string) (Subnet, error) {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return Subnet{}, err
	}
	resp, err := vn.UpdateSubnet(ctx, core.UpdateSubnetRequest{
		SubnetId:            &subnetID,
		UpdateSubnetDetails: core.UpdateSubnetDetails{DisplayName: &displayName},
	})
	if err != nil {
		return Subnet{}, fmt.Errorf("update subnet %s: %w", subnetID, err)
	}
	return toSubnet(resp.Subnet), nil
}

// DeleteSubnet 实现 Client。
func (c *RealClient) DeleteSubnet(ctx context.Context, cred Credentials, region, subnetID string) error {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return err
	}
	if _, err := vn.DeleteSubnet(ctx, core.DeleteSubnetRequest{SubnetId: &subnetID}); err != nil {
		return fmt.Errorf("delete subnet %s: %w", subnetID, err)
	}
	return nil
}

// ListSecurityLists 实现 Client；vcnID 为空时列出全部安全列表。
func (c *RealClient) ListSecurityLists(ctx context.Context, cred Credentials, region, vcnID string) ([]SecurityList, error) {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return nil, err
	}
	compartmentID, err := c.vcnScopedCompartment(ctx, cred, region, vcnID)
	if err != nil {
		return nil, err
	}
	var lists []SecurityList
	var page *string
	for {
		req := core.ListSecurityListsRequest{CompartmentId: &compartmentID, Page: page}
		if vcnID != "" {
			req.VcnId = &vcnID
		}
		resp, err := vn.ListSecurityLists(ctx, req)
		if err != nil {
			return nil, fmt.Errorf("list security lists: %w", err)
		}
		for _, item := range resp.Items {
			lists = append(lists, toSecurityList(item))
		}
		if resp.OpcNextPage == nil {
			return orEmpty(lists), nil
		}
		page = resp.OpcNextPage
	}
}

// CreateSecurityList 实现 Client。
// 安全列表跟随其 VCN 所在 compartment 创建，避免跨 compartment 悬挂资源。
func (c *RealClient) CreateSecurityList(ctx context.Context, cred Credentials, in CreateSecurityListInput) (SecurityList, error) {
	vn, err := c.vcnClient(cred, in.Region)
	if err != nil {
		return SecurityList{}, err
	}
	compartmentID, err := c.vcnScopedCompartment(ctx, cred, in.Region, in.VcnID)
	if err != nil {
		return SecurityList{}, err
	}
	resp, err := vn.CreateSecurityList(ctx, core.CreateSecurityListRequest{
		CreateSecurityListDetails: core.CreateSecurityListDetails{
			CompartmentId:        &compartmentID,
			VcnId:                &in.VcnID,
			DisplayName:          &in.DisplayName,
			IngressSecurityRules: toIngressRules(in.IngressRules),
			EgressSecurityRules:  toEgressRules(in.EgressRules),
		},
	})
	if err != nil {
		return SecurityList{}, fmt.Errorf("create security list: %w", err)
	}
	return toSecurityList(resp.SecurityList), nil
}

// GetSecurityList 实现 Client。
func (c *RealClient) GetSecurityList(ctx context.Context, cred Credentials, region, securityListID string) (SecurityList, error) {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return SecurityList{}, err
	}
	resp, err := vn.GetSecurityList(ctx, core.GetSecurityListRequest{SecurityListId: &securityListID})
	if err != nil {
		return SecurityList{}, fmt.Errorf("get security list %s: %w", securityListID, err)
	}
	return toSecurityList(resp.SecurityList), nil
}

// UpdateSecurityList 实现 Client。
func (c *RealClient) UpdateSecurityList(ctx context.Context, cred Credentials, securityListID string, in UpdateSecurityListInput) (SecurityList, error) {
	vn, err := c.vcnClient(cred, in.Region)
	if err != nil {
		return SecurityList{}, err
	}
	details := core.UpdateSecurityListDetails{}
	if in.DisplayName != "" {
		details.DisplayName = &in.DisplayName
	}
	if in.IngressRules != nil {
		details.IngressSecurityRules = toIngressRules(*in.IngressRules)
	}
	if in.EgressRules != nil {
		details.EgressSecurityRules = toEgressRules(*in.EgressRules)
	}
	resp, err := vn.UpdateSecurityList(ctx, core.UpdateSecurityListRequest{
		SecurityListId:            &securityListID,
		UpdateSecurityListDetails: details,
	})
	if err != nil {
		return SecurityList{}, fmt.Errorf("update security list %s: %w", securityListID, err)
	}
	return toSecurityList(resp.SecurityList), nil
}

// DeleteSecurityList 实现 Client。
func (c *RealClient) DeleteSecurityList(ctx context.Context, cred Credentials, region, securityListID string) error {
	vn, err := c.vcnClient(cred, region)
	if err != nil {
		return err
	}
	if _, err := vn.DeleteSecurityList(ctx, core.DeleteSecurityListRequest{SecurityListId: &securityListID}); err != nil {
		return fmt.Errorf("delete security list %s: %w", securityListID, err)
	}
	return nil
}

func toVCN(v core.Vcn) VCN {
	return VCN{
		ID:                    deref(v.Id),
		DisplayName:           deref(v.DisplayName),
		LifecycleState:        string(v.LifecycleState),
		CompartmentID:         deref(v.CompartmentId),
		CidrBlocks:            orEmpty(v.CidrBlocks),
		Ipv6CidrBlocks:        orEmpty(v.Ipv6CidrBlocks),
		DnsLabel:              deref(v.DnsLabel),
		DefaultRouteTableID:   deref(v.DefaultRouteTableId),
		DefaultSecurityListID: deref(v.DefaultSecurityListId),
		TimeCreated:           sdkTime(v.TimeCreated),
	}
}

func toSubnet(s core.Subnet) Subnet {
	return Subnet{
		ID:                 deref(s.Id),
		VcnID:              deref(s.VcnId),
		DisplayName:        deref(s.DisplayName),
		LifecycleState:     string(s.LifecycleState),
		CidrBlock:          deref(s.CidrBlock),
		Ipv6CidrBlock:      deref(s.Ipv6CidrBlock),
		DnsLabel:           deref(s.DnsLabel),
		ProhibitPublicIP:   s.ProhibitPublicIpOnVnic != nil && *s.ProhibitPublicIpOnVnic,
		AvailabilityDomain: deref(s.AvailabilityDomain),
		RouteTableID:       deref(s.RouteTableId),
		TimeCreated:        sdkTime(s.TimeCreated),
	}
}

func toSecurityList(sl core.SecurityList) SecurityList {
	out := SecurityList{
		ID:             deref(sl.Id),
		VcnID:          deref(sl.VcnId),
		DisplayName:    deref(sl.DisplayName),
		LifecycleState: string(sl.LifecycleState),
		TimeCreated:    sdkTime(sl.TimeCreated),
		IngressRules:   make([]SecurityRule, 0, len(sl.IngressSecurityRules)),
		EgressRules:    make([]SecurityRule, 0, len(sl.EgressSecurityRules)),
	}
	for _, r := range sl.IngressSecurityRules {
		rule := SecurityRule{
			Protocol:    deref(r.Protocol),
			Source:      deref(r.Source),
			IsStateless: r.IsStateless != nil && *r.IsStateless,
			Description: deref(r.Description),
		}
		fillRuleOptions(&rule, r.TcpOptions, r.UdpOptions, r.IcmpOptions)
		out.IngressRules = append(out.IngressRules, rule)
	}
	for _, r := range sl.EgressSecurityRules {
		rule := SecurityRule{
			Protocol:    deref(r.Protocol),
			Destination: deref(r.Destination),
			IsStateless: r.IsStateless != nil && *r.IsStateless,
			Description: deref(r.Description),
		}
		fillRuleOptions(&rule, r.TcpOptions, r.UdpOptions, r.IcmpOptions)
		out.EgressRules = append(out.EgressRules, rule)
	}
	return out
}

func fillRuleOptions(rule *SecurityRule, tcp *core.TcpOptions, udp *core.UdpOptions, icmp *core.IcmpOptions) {
	switch {
	case tcp != nil && tcp.DestinationPortRange != nil:
		rule.PortMin = tcp.DestinationPortRange.Min
		rule.PortMax = tcp.DestinationPortRange.Max
	case udp != nil && udp.DestinationPortRange != nil:
		rule.PortMin = udp.DestinationPortRange.Min
		rule.PortMax = udp.DestinationPortRange.Max
	case icmp != nil:
		rule.IcmpType = icmp.Type
		rule.IcmpCode = icmp.Code
	}
}

func toIngressRules(rules []SecurityRule) []core.IngressSecurityRule {
	out := make([]core.IngressSecurityRule, 0, len(rules))
	for i := range rules {
		r := rules[i]
		sdk := core.IngressSecurityRule{
			Protocol:    &r.Protocol,
			Source:      &r.Source,
			IsStateless: &rules[i].IsStateless,
		}
		if r.Description != "" {
			sdk.Description = &rules[i].Description
		}
		sdk.TcpOptions, sdk.UdpOptions, sdk.IcmpOptions = buildRuleOptions(r)
		out = append(out, sdk)
	}
	return out
}

func toEgressRules(rules []SecurityRule) []core.EgressSecurityRule {
	out := make([]core.EgressSecurityRule, 0, len(rules))
	for i := range rules {
		r := rules[i]
		sdk := core.EgressSecurityRule{
			Protocol:    &r.Protocol,
			Destination: &r.Destination,
			IsStateless: &rules[i].IsStateless,
		}
		if r.Description != "" {
			sdk.Description = &rules[i].Description
		}
		sdk.TcpOptions, sdk.UdpOptions, sdk.IcmpOptions = buildRuleOptions(r)
		out = append(out, sdk)
	}
	return out
}

// buildRuleOptions 按协议号构造端口或 ICMP 选项：6=TCP、17=UDP、1/58=ICMP(v6)。
func buildRuleOptions(r SecurityRule) (*core.TcpOptions, *core.UdpOptions, *core.IcmpOptions) {
	if r.PortMin != nil {
		portMax := r.PortMax
		if portMax == nil {
			portMax = r.PortMin
		}
		pr := &core.PortRange{Min: r.PortMin, Max: portMax}
		switch r.Protocol {
		case "6":
			return &core.TcpOptions{DestinationPortRange: pr}, nil, nil
		case "17":
			return nil, &core.UdpOptions{DestinationPortRange: pr}, nil
		}
	}
	if r.IcmpType != nil && (r.Protocol == "1" || r.Protocol == "58") {
		return nil, nil, &core.IcmpOptions{Type: r.IcmpType, Code: r.IcmpCode}
	}
	return nil, nil, nil
}
