package oci

import (
	"context"
	"fmt"
	"time"

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

// shapeCacheTTL 是 shape 清单缓存时长：区域提供的 shape 极少变化。
const shapeCacheTTL = 6 * time.Hour

// ComputeShape 是租户在目标区域实际可用的一种实例规格。
// Flex 规格带 Ocpus/Memory 的可选范围；固定规格带其固定值。
type ComputeShape struct {
	Name                 string  `json:"name"`
	BillingType          string  `json:"billingType"` // ALWAYS_FREE / LIMITED_FREE / PAID
	IsFlexible           bool    `json:"isFlexible"`
	Ocpus                float32 `json:"ocpus,omitempty"`
	MemoryInGBs          float32 `json:"memoryInGBs,omitempty"`
	OcpusMin             float32 `json:"ocpusMin,omitempty"`
	OcpusMax             float32 `json:"ocpusMax,omitempty"`
	MemoryMinGBs         float32 `json:"memoryMinGBs,omitempty"`
	MemoryMaxGBs         float32 `json:"memoryMaxGBs,omitempty"`
	Gpus                 int     `json:"gpus,omitempty"`
	ProcessorDescription string  `json:"processorDescription,omitempty"`
}

// shapeCacheEntry 是一份 shape 清单的缓存条目。
type shapeCacheEntry struct {
	expires time.Time
	shapes  []ComputeShape
}

// ListShapes 实现 Client：列出租户在目标区域可用的实例规格（带 TTL 缓存）。
// availabilityDomain 为空时返回区域级去重清单。
func (c *RealClient) ListShapes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]ComputeShape, error) {
	key := cred.TenancyOCID + "|" + region + "|" + availabilityDomain
	if v, ok := c.shapes.Load(key); ok {
		if e := v.(shapeCacheEntry); time.Now().Before(e.expires) {
			return e.shapes, nil
		}
	}
	shapes, err := c.fetchShapes(ctx, cred, region, availabilityDomain)
	if err != nil {
		return nil, err
	}
	c.shapes.Store(key, shapeCacheEntry{expires: time.Now().Add(shapeCacheTTL), shapes: shapes})
	return shapes, nil
}

// fetchShapes 分页拉取 shape 清单并按名称去重（不带 AD 时 OCI 每个可用域各返回一份）。
func (c *RealClient) fetchShapes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]ComputeShape, error) {
	cc, err := c.computeClient(cred, region)
	if err != nil {
		return nil, err
	}
	shapes := []ComputeShape{}
	seen := map[string]bool{}
	var page *string
	for {
		req := core.ListShapesRequest{
			CompartmentId: cred.EffectiveCompartment(),
			Limit:         common.Int(500),
			Page:          page,
		}
		if availabilityDomain != "" {
			req.AvailabilityDomain = &availabilityDomain
		}
		resp, err := cc.ListShapes(ctx, req)
		if err != nil {
			return nil, fmt.Errorf("list shapes: %w", err)
		}
		for _, item := range resp.Items {
			if name := deref(item.Shape); !seen[name] {
				seen[name] = true
				shapes = append(shapes, toComputeShape(item))
			}
		}
		if resp.OpcNextPage == nil {
			return shapes, nil
		}
		page = resp.OpcNextPage
	}
}

func toComputeShape(s core.Shape) ComputeShape {
	out := ComputeShape{
		Name:                 deref(s.Shape),
		BillingType:          string(s.BillingType),
		IsFlexible:           s.OcpuOptions != nil,
		ProcessorDescription: deref(s.ProcessorDescription),
	}
	out.Ocpus, out.MemoryInGBs = deref32(s.Ocpus), deref32(s.MemoryInGBs)
	if s.Gpus != nil {
		out.Gpus = *s.Gpus
	}
	if o := s.OcpuOptions; o != nil {
		out.OcpusMin, out.OcpusMax = deref32(o.Min), deref32(o.Max)
	}
	if m := s.MemoryOptions; m != nil {
		out.MemoryMinGBs, out.MemoryMaxGBs = deref32(m.MinInGBs), deref32(m.MaxInGBs)
	}
	return out
}

// deref32 解引用 *float32，nil 时返回零值。
func deref32(p *float32) float32 {
	if p == nil {
		return 0
	}
	return *p
}
