package service

import (
	"context"
	"fmt"

	"gorm.io/gorm"

	"oci-portal/internal/model"
	"oci-portal/internal/oci"
)

// syncScopeCaches 按配置开关同步区域 / 区间缓存：开启则整组刷新，关闭则清空。
// 随测活（导入 / 修改 / 测活按钮 / 定时任务）触发；OCI 调用失败不影响测活主流程。
func (s *OciConfigService) syncScopeCaches(ctx context.Context, cfg *model.OciConfig, cred oci.Credentials) {
	if cfg.MultiRegion {
		if subs, err := s.client.ListRegionSubscriptions(ctx, cred); err == nil {
			_ = s.saveRegionCache(ctx, cfg.ID, subs)
		}
	} else {
		s.db.WithContext(ctx).Where("oci_config_id = ?", cfg.ID).Delete(&model.RegionCache{})
	}
	if cfg.MultiCompartment {
		if comps, err := s.client.ListCompartments(ctx, cred); err == nil {
			_ = s.saveCompartmentCache(ctx, cfg.ID, comps)
		}
	} else {
		s.db.WithContext(ctx).Where("oci_config_id = ?", cfg.ID).Delete(&model.CompartmentCache{})
	}
}

// saveRegionCache 整组覆盖写入订阅区域缓存。
func (s *OciConfigService) saveRegionCache(ctx context.Context, cfgID uint, subs []oci.RegionSubscription) error {
	rows := make([]model.RegionCache, 0, len(subs))
	for _, sub := range subs {
		rows = append(rows, model.RegionCache{
			OciConfigID: cfgID, Key: sub.Key, Name: sub.Name,
			Status: sub.Status, IsHomeRegion: sub.IsHomeRegion,
		})
	}
	return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := lockOciConfig(tx, cfgID); err != nil {
			return err
		}
		if err := tx.Where("oci_config_id = ?", cfgID).Delete(&model.RegionCache{}).Error; err != nil {
			return err
		}
		if len(rows) == 0 {
			return nil
		}
		return tx.Create(&rows).Error
	})
}

// saveCompartmentCache 整组覆盖写入 compartment 缓存。
func (s *OciConfigService) saveCompartmentCache(ctx context.Context, cfgID uint, comps []oci.Compartment) error {
	rows := make([]model.CompartmentCache, 0, len(comps))
	for _, c := range comps {
		rows = append(rows, model.CompartmentCache{
			OciConfigID: cfgID, OCID: c.ID, Name: c.Name, State: c.LifecycleState,
		})
	}
	return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := lockOciConfig(tx, cfgID); err != nil {
			return err
		}
		if err := tx.Where("oci_config_id = ?", cfgID).Delete(&model.CompartmentCache{}).Error; err != nil {
			return err
		}
		if len(rows) == 0 {
			return nil
		}
		return tx.Create(&rows).Error
	})
}

// CachedRegions 返回筛选器用的区域列表：未开启多区域支持时锁定为配置默认区域；
// 缓存缺失或存在非 READY 状态（订阅进行中）时实时刷新，直到全部 READY。
func (s *OciConfigService) CachedRegions(ctx context.Context, id uint) ([]RegionSubscriptionView, error) {
	cfg, err := s.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	if !cfg.MultiRegion {
		return defaultOnlyRegion(cfg), nil
	}
	var rows []model.RegionCache
	if err := s.db.WithContext(ctx).Where("oci_config_id = ?", id).Find(&rows).Error; err != nil {
		return nil, fmt.Errorf("load region cache: %w", err)
	}
	if len(rows) == 0 || hasPendingRegion(rows) {
		if fresh, err := s.refreshRegionCache(ctx, cfg); err == nil {
			return fresh, nil
		}
		// 实时刷新失败时退回现有缓存，避免筛选器整体不可用
	}
	return regionCacheViews(rows), nil
}

// refreshRegionCache 实时拉取订阅区域并覆盖缓存。
func (s *OciConfigService) refreshRegionCache(ctx context.Context, cfg *model.OciConfig) ([]RegionSubscriptionView, error) {
	cred, err := s.credentialsOf(cfg)
	if err != nil {
		return nil, err
	}
	subs, err := s.client.ListRegionSubscriptions(ctx, cred)
	if err != nil {
		return nil, err
	}
	if err := s.saveRegionCache(ctx, cfg.ID, subs); err != nil {
		return nil, err
	}
	return enrichRegionAlias(subs), nil
}

// reconcileRegionCache 把 SDK 实时数据与缓存比较，有差异时覆盖缓存。
func (s *OciConfigService) reconcileRegionCache(ctx context.Context, cfgID uint, subs []oci.RegionSubscription) {
	var rows []model.RegionCache
	if err := s.db.WithContext(ctx).Where("oci_config_id = ?", cfgID).Find(&rows).Error; err != nil {
		return
	}
	if regionCacheDiffers(rows, subs) {
		_ = s.saveRegionCache(ctx, cfgID, subs)
	}
}

func regionCacheDiffers(rows []model.RegionCache, subs []oci.RegionSubscription) bool {
	if len(rows) != len(subs) {
		return true
	}
	byKey := make(map[string]model.RegionCache, len(rows))
	for _, r := range rows {
		byKey[r.Key] = r
	}
	for _, sub := range subs {
		r, ok := byKey[sub.Key]
		if !ok || r.Status != sub.Status || r.Name != sub.Name || r.IsHomeRegion != sub.IsHomeRegion {
			return true
		}
	}
	return false
}

func hasPendingRegion(rows []model.RegionCache) bool {
	for _, r := range rows {
		if r.Status != regionStatusReady {
			return true
		}
	}
	return false
}

// regionStatusReady 是 OCI 区域订阅完成态。
const regionStatusReady = "READY"

// defaultOnlyRegion 为未开启多区域支持的配置合成单元素列表（配置默认区域）。
func defaultOnlyRegion(cfg *model.OciConfig) []RegionSubscriptionView {
	sub := oci.RegionSubscription{
		Key: cfg.HomeRegionKey, Name: cfg.Region, Status: regionStatusReady,
	}
	if info, ok := oci.RegionByKey(cfg.HomeRegionKey); ok {
		sub.IsHomeRegion = info.Name == cfg.Region
	}
	return enrichRegionAlias([]oci.RegionSubscription{sub})
}

func regionCacheViews(rows []model.RegionCache) []RegionSubscriptionView {
	subs := make([]oci.RegionSubscription, 0, len(rows))
	for _, r := range rows {
		subs = append(subs, oci.RegionSubscription{
			Key: r.Key, Name: r.Name, Status: r.Status, IsHomeRegion: r.IsHomeRegion,
		})
	}
	// 与实时接口一致：主区域置顶，其余按 alias 字典序
	oci.SortRegionSubscriptions(subs)
	return enrichRegionAlias(subs)
}

// CachedCompartments 返回筛选器用的区间列表：未开启多区间支持返回空（前端锁定根）；
// 已开启但缓存为空时实时拉取一次入库。
func (s *OciConfigService) CachedCompartments(ctx context.Context, id uint) ([]oci.Compartment, error) {
	cfg, err := s.Get(ctx, id)
	if err != nil {
		return nil, err
	}
	if !cfg.MultiCompartment {
		return []oci.Compartment{}, nil
	}
	var rows []model.CompartmentCache
	if err := s.db.WithContext(ctx).Where("oci_config_id = ?", id).
		Order("name").Find(&rows).Error; err != nil {
		return nil, fmt.Errorf("load compartment cache: %w", err)
	}
	if len(rows) == 0 {
		return s.refreshCompartmentCache(ctx, cfg)
	}
	comps := make([]oci.Compartment, 0, len(rows))
	for _, r := range rows {
		comps = append(comps, oci.Compartment{ID: r.OCID, Name: r.Name, LifecycleState: r.State})
	}
	return comps, nil
}

// refreshCompartmentCache 实时拉取 compartment 并覆盖缓存。
func (s *OciConfigService) refreshCompartmentCache(ctx context.Context, cfg *model.OciConfig) ([]oci.Compartment, error) {
	cred, err := s.credentialsOf(cfg)
	if err != nil {
		return nil, err
	}
	comps, err := s.client.ListCompartments(ctx, cred)
	if err != nil {
		return nil, err
	}
	if err := s.saveCompartmentCache(ctx, cfg.ID, comps); err != nil {
		return nil, err
	}
	return comps, nil
}
