package oci

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"sync"
	"time"

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

// maxAvailabilityLookups 限制附带用量查询的条数：
// GetResourceAvailability 每条配额一次调用（OCI 无批量接口），
// 定义预筛 + 并发查询后 100 条约需 1~3 秒。
const maxAvailabilityLookups = 100

// limitsPageSize 是 limits 系列 List 接口的显式分页大小（OCI 通用上限 1000）；
// 不传时服务端默认约百条一页，全量拉取要反复翻页。
const limitsPageSize = 1000

// availabilityConcurrency 是用量查询并发度；Limits 端点 TPS 不高，并发过高易撞限流。
const availabilityConcurrency = 8

// limitDefTTL 是配额定义缓存时长：定义（是否支持用量查询）几乎不随时间变化。
const limitDefTTL = 6 * time.Hour

// RegionSubscription 是租户对一个区域的订阅状态。
type RegionSubscription struct {
	Key          string `json:"key"`
	Name         string `json:"name"`
	Status       string `json:"status"`
	IsHomeRegion bool   `json:"isHomeRegion"`
}

// LimitValue 是一条服务配额；Used/Available 仅在请求用量时填充，
// 且 OCI 对部分配额不提供用量数据，此时保持为 nil。
type LimitValue struct {
	Name               string `json:"name"`
	ScopeType          string `json:"scopeType"`
	AvailabilityDomain string `json:"availabilityDomain,omitempty"`
	Value              int64  `json:"value"`
	Used               *int64 `json:"used,omitempty"`
	Available          *int64 `json:"available,omitempty"`
}

// LimitsQuery 是一次配额查询的条件。
type LimitsQuery struct {
	Region             string // 目标区域，空则用凭据默认区域
	Service            string // 服务名，如 compute
	Name               string // 可选，配额名模糊过滤（大小写不敏感子串，本地过滤）
	ScopeType          string // 可选 GLOBAL / REGION / AD，下推给 OCI 过滤
	AvailabilityDomain string // 可选，AD 全名，下推给 OCI 过滤
	NonZero            bool   // 过滤掉上限为 0 的配额项（在用量查询前生效，不占并发名额）
	WithAvailability   bool   // 并发附带 used/available
}

// LimitService 是配额体系支持的一个服务。
type LimitService struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

// ListRegionSubscriptions 实现 Client。
func (c *RealClient) ListRegionSubscriptions(ctx context.Context, cred Credentials) ([]RegionSubscription, error) {
	ic, err := identity.NewIdentityClientWithConfigurationProvider(provider(cred))
	if err != nil {
		return nil, fmt.Errorf("new identity client: %w", err)
	}
	applyProxy(&ic.BaseClient, cred)
	resp, err := ic.ListRegionSubscriptions(ctx, identity.ListRegionSubscriptionsRequest{
		TenancyId: &cred.TenancyOCID,
	})
	if err != nil {
		return nil, fmt.Errorf("list region subscriptions: %w", err)
	}
	subs := make([]RegionSubscription, 0, len(resp.Items))
	for _, item := range resp.Items {
		subs = append(subs, RegionSubscription{
			Key:          deref(item.RegionKey),
			Name:         deref(item.RegionName),
			Status:       string(item.Status),
			IsHomeRegion: item.IsHomeRegion != nil && *item.IsHomeRegion,
		})
	}
	SortRegionSubscriptions(subs)
	return subs, nil
}

// SortRegionSubscriptions 主区域置顶，其余按友好名（alias）字典序，
// 本地表查不到 alias 时回退区域名；实时接口与缓存读取共用，全局统一展示顺序。
func SortRegionSubscriptions(subs []RegionSubscription) {
	sortKey := func(sub RegionSubscription) string {
		if info, ok := RegionByKey(sub.Key); ok {
			return info.Alias
		}
		return sub.Name
	}
	sort.SliceStable(subs, func(i, j int) bool {
		if subs[i].IsHomeRegion != subs[j].IsHomeRegion {
			return subs[i].IsHomeRegion
		}
		return sortKey(subs[i]) < sortKey(subs[j])
	})
}

// SubscribeRegion 实现 Client：订阅新区域。OCI 要求该请求发往
// home region，且区域订阅一经创建不可取消。
func (c *RealClient) SubscribeRegion(ctx context.Context, cred Credentials, homeRegion, regionKey string) error {
	ic, err := identity.NewIdentityClientWithConfigurationProvider(provider(cred))
	if err != nil {
		return fmt.Errorf("new identity client: %w", err)
	}
	applyProxy(&ic.BaseClient, cred)
	if homeRegion != "" {
		ic.SetRegion(normalizeRegion(homeRegion))
	}
	_, err = ic.CreateRegionSubscription(ctx, identity.CreateRegionSubscriptionRequest{
		TenancyId: &cred.TenancyOCID,
		CreateRegionSubscriptionDetails: identity.CreateRegionSubscriptionDetails{
			RegionKey: &regionKey,
		},
	})
	if err != nil {
		return fmt.Errorf("create region subscription %s: %w", regionKey, err)
	}
	return nil
}

// limitsClient 构造 limits 服务客户端；region 为空时用凭据默认区域。
func limitsClient(cred Credentials, region string) (limits.LimitsClient, error) {
	lc, err := limits.NewLimitsClientWithConfigurationProvider(provider(cred))
	if err != nil {
		return lc, fmt.Errorf("new limits client: %w", err)
	}
	applyProxy(&lc.BaseClient, cred)
	if region != "" {
		lc.SetRegion(normalizeRegion(region))
	}
	return lc, nil
}

// ListLimits 实现 Client：全量拉取配额（scopeType / AD 条件下推），
// 名称本地子串过滤，按需并发附带用量。
func (c *RealClient) ListLimits(ctx context.Context, cred Credentials, q LimitsQuery) ([]LimitValue, error) {
	lc, err := limitsClient(cred, q.Region)
	if err != nil {
		return nil, err
	}
	values, err := listLimitValues(ctx, lc, cred.TenancyOCID, q)
	if err != nil {
		return nil, err
	}
	values = filterLimitsByName(values, q.Name)
	if q.NonZero {
		values = filterNonZero(values)
	}
	if !q.WithAvailability {
		return values, nil
	}
	if len(values) > maxAvailabilityLookups {
		return nil, fmt.Errorf("list limits: %d entries exceed availability lookup cap %d, narrow with name filter",
			len(values), maxAvailabilityLookups)
	}
	supported := c.availabilitySupport(ctx, lc, cred.TenancyOCID, q.Region, q.Service)
	attachAvailability(ctx, lc, cred.TenancyOCID, q.Service, values, supported)
	return values, nil
}

// listLimitValues 分页取回一个服务的配额；scopeType / availabilityDomain 下推给 OCI。
// OCI 的 name 参数只支持精确匹配，子串过滤由调用方本地完成。
func listLimitValues(ctx context.Context, lc limits.LimitsClient, tenancyOCID string, q LimitsQuery) ([]LimitValue, error) {
	var values []LimitValue
	var page *string
	for {
		req := limits.ListLimitValuesRequest{
			CompartmentId: &tenancyOCID,
			ServiceName:   &q.Service,
			Limit:         common.Int(limitsPageSize),
			Page:          page,
		}
		if q.ScopeType != "" {
			req.ScopeType = limits.ListLimitValuesScopeTypeEnum(q.ScopeType)
		}
		if q.AvailabilityDomain != "" {
			req.AvailabilityDomain = &q.AvailabilityDomain
		}
		resp, err := lc.ListLimitValues(ctx, req)
		if err != nil {
			return nil, fmt.Errorf("list limit values of %s: %w", q.Service, err)
		}
		for _, item := range resp.Items {
			values = append(values, toLimitValue(item))
		}
		if resp.OpcNextPage == nil {
			return orEmpty(values), nil
		}
		page = resp.OpcNextPage
	}
}

func toLimitValue(item limits.LimitValueSummary) LimitValue {
	var value int64
	if item.Value != nil {
		value = *item.Value
	}
	return LimitValue{
		Name:               deref(item.Name),
		ScopeType:          string(item.ScopeType),
		AvailabilityDomain: deref(item.AvailabilityDomain),
		Value:              value,
	}
}

// filterLimitsByName 按大小写不敏感的子串匹配过滤配额名。
func filterLimitsByName(values []LimitValue, name string) []LimitValue {
	if name == "" {
		return values
	}
	needle := strings.ToLower(strings.TrimSpace(name))
	filtered := make([]LimitValue, 0, len(values))
	for _, v := range values {
		if strings.Contains(strings.ToLower(v.Name), needle) {
			filtered = append(filtered, v)
		}
	}
	return filtered
}

// filterNonZero 过滤掉上限为 0 的配额项。
func filterNonZero(values []LimitValue) []LimitValue {
	filtered := make([]LimitValue, 0, len(values))
	for _, v := range values {
		if v.Value > 0 {
			filtered = append(filtered, v)
		}
	}
	return filtered
}

// ListLimitServices 实现 Client：分页列出配额体系支持的全部服务。
func (c *RealClient) ListLimitServices(ctx context.Context, cred Credentials, region string) ([]LimitService, error) {
	lc, err := limitsClient(cred, region)
	if err != nil {
		return nil, err
	}
	var services []LimitService
	var page *string
	for {
		resp, err := lc.ListServices(ctx, limits.ListServicesRequest{
			CompartmentId: &cred.TenancyOCID,
			Limit:         common.Int(limitsPageSize),
			Page:          page,
		})
		if err != nil {
			return nil, fmt.Errorf("list limit services: %w", err)
		}
		for _, item := range resp.Items {
			services = append(services, LimitService{
				Name:        deref(item.Name),
				Description: deref(item.Description),
			})
		}
		if resp.OpcNextPage == nil {
			return orEmpty(services), nil
		}
		page = resp.OpcNextPage
	}
}

// limitDefEntry 是一份配额定义预筛结果的缓存条目。
type limitDefEntry struct {
	expires   time.Time
	supported map[string]bool
}

// availabilitySupport 返回配额名 → 是否支持用量查询的映射（带 TTL 缓存）；
// 拉取失败时返回 nil，调用方视为全部照查（保守降级）。
func (c *RealClient) availabilitySupport(ctx context.Context, lc limits.LimitsClient, tenancyOCID, region, service string) map[string]bool {
	key := tenancyOCID + "|" + region + "|" + service
	if v, ok := c.limitDefs.Load(key); ok {
		if e := v.(limitDefEntry); time.Now().Before(e.expires) {
			return e.supported
		}
	}
	supported, err := listLimitDefinitions(ctx, lc, tenancyOCID, service)
	if err != nil {
		return nil
	}
	c.limitDefs.Store(key, limitDefEntry{expires: time.Now().Add(limitDefTTL), supported: supported})
	return supported
}

// listLimitDefinitions 分页取回一个服务的配额定义，产出配额名 → 是否支持用量查询的映射；
// 字段缺失（nil）视为支持，仅明确 false 的会被预筛跳过。
func listLimitDefinitions(ctx context.Context, lc limits.LimitsClient, tenancyOCID, service string) (map[string]bool, error) {
	supported := map[string]bool{}
	var page *string
	for {
		resp, err := lc.ListLimitDefinitions(ctx, limits.ListLimitDefinitionsRequest{
			CompartmentId: &tenancyOCID,
			ServiceName:   &service,
			Limit:         common.Int(limitsPageSize),
			Page:          page,
		})
		if err != nil {
			return nil, fmt.Errorf("list limit definitions of %s: %w", service, err)
		}
		for _, item := range resp.Items {
			if item.Name == nil {
				continue
			}
			supported[*item.Name] = item.IsResourceAvailabilitySupported == nil || *item.IsResourceAvailabilitySupported
		}
		if resp.OpcNextPage == nil {
			return supported, nil
		}
		page = resp.OpcNextPage
	}
}

// attachAvailability 并发查询用量：预筛掉明确不支持用量查询的配额，
// 单条失败不影响整体，字段保持 nil。
func attachAvailability(ctx context.Context, lc limits.LimitsClient, tenancyOCID, service string, values []LimitValue, supported map[string]bool) {
	sem := make(chan struct{}, availabilityConcurrency)
	var wg sync.WaitGroup
	for i := range values {
		if s, ok := supported[values[i].Name]; ok && !s {
			continue
		}
		wg.Add(1)
		sem <- struct{}{}
		go func(v *LimitValue) {
			defer wg.Done()
			defer func() { <-sem }()
			fetchAvailability(ctx, lc, tenancyOCID, service, v)
		}(&values[i])
	}
	wg.Wait()
}

// fetchAvailability 查询单条配额的用量；AD 级配额必须带 availabilityDomain。
func fetchAvailability(ctx context.Context, lc limits.LimitsClient, tenancyOCID, service string, v *LimitValue) {
	req := limits.GetResourceAvailabilityRequest{
		ServiceName:   &service,
		LimitName:     &v.Name,
		CompartmentId: &tenancyOCID,
	}
	if strings.EqualFold(v.ScopeType, "AD") {
		req.AvailabilityDomain = &v.AvailabilityDomain
	}
	resp, err := lc.GetResourceAvailability(ctx, req)
	if err != nil {
		return
	}
	v.Used = resp.Used
	v.Available = resp.Available
}
