package oci

import (
	"context"
	"slices"
	"strings"
	"time"

	"oci-portal/internal/cache"
)

// 读缓存 TTL 分级:过渡态资源短、准静态长(调研①方案 A)。
// 实例列表 10s:前端 5s 自动刷新隔次命中,OCI 调用减半,状态延迟可控。
const (
	cacheTTLInstances = 10 * time.Second
	cacheTTLVolatile  = 30 * time.Second // 卷 / 挂载 / 网卡
	cacheTTLNetwork   = 2 * time.Minute  // VCN / 子网 / 安全列表
	cacheTTLStatic    = 10 * time.Minute // 镜像 / 可用域
)

// CachedClient 用进程内 TTL 缓存包装真实客户端,削减 OCI 读放大;
// 写操作直通并失效同租户全部读缓存(粗粒度换正确性,重建仅一次回源)。
// 未覆写的方法经嵌入接口直通。
type CachedClient struct {
	Client
	store *cache.Cache
}

// NewCachedClient 包装 inner;上限 4096 键(单键几 KB,整体几 MB 级)。
func NewCachedClient(inner Client) *CachedClient {
	return &CachedClient{Client: inner, store: cache.New(4096)}
}

// ckey 组缓存键;租户 OCID 在最前,写失效按前缀一锅端。
func ckey(cred Credentials, parts ...string) string {
	return cred.TenancyOCID + "|" + strings.Join(parts, "|")
}

// bust 写操作成功后失效该租户全部读缓存。
func (c *CachedClient) bust(err error, cred Credentials) {
	if err == nil {
		c.InvalidateTenancy(cred.TenancyOCID)
	}
}

// InvalidateTenancy 主动失效指定 tenancy 的全部 OCI 读缓存。
func (c *CachedClient) InvalidateTenancy(tenancyOCID string) {
	if tenancyOCID == "" {
		return
	}
	c.store.DeletePrefix(tenancyOCID + "|")
}

// cachedList 缓存切片结果;命中返回浅拷贝,防调用方排序共享底层数组。
func cachedList[T any](c *CachedClient, key string, ttl time.Duration, fn func() ([]T, error)) ([]T, error) {
	v, err := cache.Do(c.store, key, ttl, fn)
	if err != nil {
		return nil, err
	}
	return slices.Clone(v), nil
}

// ---- 读:按 TTL 分级缓存 ----

func (c *CachedClient) ListInstances(ctx context.Context, cred Credentials, region string) ([]Instance, error) {
	return cachedList(c, ckey(cred, "instances", region), cacheTTLInstances, func() ([]Instance, error) {
		return c.Client.ListInstances(ctx, cred, region)
	})
}

func (c *CachedClient) ListImages(ctx context.Context, cred Credentials, q ImagesQuery) ([]Image, error) {
	return cachedList(c, ckey(cred, "images", q.Region, q.OperatingSystem, q.Shape), cacheTTLStatic, func() ([]Image, error) {
		return c.Client.ListImages(ctx, cred, q)
	})
}

func (c *CachedClient) ListVCNs(ctx context.Context, cred Credentials, region string) ([]VCN, error) {
	return cachedList(c, ckey(cred, "vcns", region), cacheTTLNetwork, func() ([]VCN, error) {
		return c.Client.ListVCNs(ctx, cred, region)
	})
}

func (c *CachedClient) ListSubnets(ctx context.Context, cred Credentials, region, vcnID string) ([]Subnet, error) {
	return cachedList(c, ckey(cred, "subnets", region, vcnID), cacheTTLNetwork, func() ([]Subnet, error) {
		return c.Client.ListSubnets(ctx, cred, region, vcnID)
	})
}

func (c *CachedClient) ListSecurityLists(ctx context.Context, cred Credentials, region, vcnID string) ([]SecurityList, error) {
	return cachedList(c, ckey(cred, "seclists", region, vcnID), cacheTTLNetwork, func() ([]SecurityList, error) {
		return c.Client.ListSecurityLists(ctx, cred, region, vcnID)
	})
}

func (c *CachedClient) ListAvailabilityDomains(ctx context.Context, cred Credentials, region string) ([]string, error) {
	return cachedList(c, ckey(cred, "ads", region), cacheTTLStatic, func() ([]string, error) {
		return c.Client.ListAvailabilityDomains(ctx, cred, region)
	})
}

func (c *CachedClient) ListBootVolumes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]BootVolume, error) {
	return cachedList(c, ckey(cred, "bootvols", region, availabilityDomain), cacheTTLVolatile, func() ([]BootVolume, error) {
		return c.Client.ListBootVolumes(ctx, cred, region, availabilityDomain)
	})
}

func (c *CachedClient) ListBlockVolumes(ctx context.Context, cred Credentials, region, availabilityDomain string) ([]BlockVolume, error) {
	return cachedList(c, ckey(cred, "blockvols", region, availabilityDomain), cacheTTLVolatile, func() ([]BlockVolume, error) {
		return c.Client.ListBlockVolumes(ctx, cred, region, availabilityDomain)
	})
}

func (c *CachedClient) ListInstanceVnics(ctx context.Context, cred Credentials, region, instanceID string) ([]Vnic, error) {
	return cachedList(c, ckey(cred, "vnics", region, instanceID), cacheTTLVolatile, func() ([]Vnic, error) {
		return c.Client.ListInstanceVnics(ctx, cred, region, instanceID)
	})
}

func (c *CachedClient) ListBootVolumeAttachments(ctx context.Context, cred Credentials, region, instanceID string) ([]BootVolumeAttachment, error) {
	return cachedList(c, ckey(cred, "bvattach", region, instanceID), cacheTTLVolatile, func() ([]BootVolumeAttachment, error) {
		return c.Client.ListBootVolumeAttachments(ctx, cred, region, instanceID)
	})
}

func (c *CachedClient) ListVolumeAttachments(ctx context.Context, cred Credentials, region, instanceID string) ([]VolumeAttachment, error) {
	return cachedList(c, ckey(cred, "volattach", region, instanceID), cacheTTLVolatile, func() ([]VolumeAttachment, error) {
		return c.Client.ListVolumeAttachments(ctx, cred, region, instanceID)
	})
}

// ListIdentityDomains 域列表准静态且是所有按域操作的 URL 解析源,长 TTL 缓存。
func (c *CachedClient) ListIdentityDomains(ctx context.Context, cred Credentials, region string) ([]IdentityDomain, error) {
	return cachedList(c, ckey(cred, "iddomains", region), cacheTTLStatic, func() ([]IdentityDomain, error) {
		return c.Client.ListIdentityDomains(ctx, cred, region)
	})
}

// ---- 写:直通,成功后按租户失效 ----

func (c *CachedClient) LaunchInstance(ctx context.Context, cred Credentials, in CreateInstanceInput) (Instance, error) {
	out, err := c.Client.LaunchInstance(ctx, cred, in)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) UpdateInstance(ctx context.Context, cred Credentials, instanceID string, in UpdateInstanceInput) (Instance, error) {
	out, err := c.Client.UpdateInstance(ctx, cred, instanceID, in)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) TerminateInstance(ctx context.Context, cred Credentials, region, instanceID string, preserveBootVolume bool) error {
	err := c.Client.TerminateInstance(ctx, cred, region, instanceID, preserveBootVolume)
	c.bust(err, cred)
	return err
}

func (c *CachedClient) InstanceAction(ctx context.Context, cred Credentials, region, instanceID, action string) (Instance, error) {
	out, err := c.Client.InstanceAction(ctx, cred, region, instanceID, action)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) ChangeInstancePublicIP(ctx context.Context, cred Credentials, region, instanceID string) (string, error) {
	out, err := c.Client.ChangeInstancePublicIP(ctx, cred, region, instanceID)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) AddInstanceIpv6(ctx context.Context, cred Credentials, region, instanceID, address string) (string, error) {
	out, err := c.Client.AddInstanceIpv6(ctx, cred, region, instanceID, address)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) DeleteInstanceIpv6(ctx context.Context, cred Credentials, region, instanceID, address string) error {
	err := c.Client.DeleteInstanceIpv6(ctx, cred, region, instanceID, address)
	c.bust(err, cred)
	return err
}

func (c *CachedClient) CreateVCN(ctx context.Context, cred Credentials, in CreateVCNInput) (VCN, error) {
	out, err := c.Client.CreateVCN(ctx, cred, in)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) UpdateVCN(ctx context.Context, cred Credentials, region, vcnID, displayName string) (VCN, error) {
	out, err := c.Client.UpdateVCN(ctx, cred, region, vcnID, displayName)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) DeleteVCN(ctx context.Context, cred Credentials, region, vcnID string) error {
	err := c.Client.DeleteVCN(ctx, cred, region, vcnID)
	c.bust(err, cred)
	return err
}

func (c *CachedClient) EnableVCNIPv6(ctx context.Context, cred Credentials, region, vcnID string) ([]IPv6Step, error) {
	out, err := c.Client.EnableVCNIPv6(ctx, cred, region, vcnID)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) CreateSubnet(ctx context.Context, cred Credentials, in CreateSubnetInput) (Subnet, error) {
	out, err := c.Client.CreateSubnet(ctx, cred, in)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) UpdateSubnet(ctx context.Context, cred Credentials, region, subnetID, displayName string) (Subnet, error) {
	out, err := c.Client.UpdateSubnet(ctx, cred, region, subnetID, displayName)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) DeleteSubnet(ctx context.Context, cred Credentials, region, subnetID string) error {
	err := c.Client.DeleteSubnet(ctx, cred, region, subnetID)
	c.bust(err, cred)
	return err
}

func (c *CachedClient) CreateSecurityList(ctx context.Context, cred Credentials, in CreateSecurityListInput) (SecurityList, error) {
	out, err := c.Client.CreateSecurityList(ctx, cred, in)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) UpdateSecurityList(ctx context.Context, cred Credentials, securityListID string, in UpdateSecurityListInput) (SecurityList, error) {
	out, err := c.Client.UpdateSecurityList(ctx, cred, securityListID, in)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) DeleteSecurityList(ctx context.Context, cred Credentials, region, securityListID string) error {
	err := c.Client.DeleteSecurityList(ctx, cred, region, securityListID)
	c.bust(err, cred)
	return err
}

func (c *CachedClient) UpdateBootVolume(ctx context.Context, cred Credentials, bootVolumeID string, in UpdateBootVolumeInput) (BootVolume, error) {
	out, err := c.Client.UpdateBootVolume(ctx, cred, bootVolumeID, in)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) DeleteBootVolume(ctx context.Context, cred Credentials, region, bootVolumeID string) error {
	err := c.Client.DeleteBootVolume(ctx, cred, region, bootVolumeID)
	c.bust(err, cred)
	return err
}

func (c *CachedClient) AttachBootVolume(ctx context.Context, cred Credentials, region, instanceID, bootVolumeID string) (BootVolumeAttachment, error) {
	out, err := c.Client.AttachBootVolume(ctx, cred, region, instanceID, bootVolumeID)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) DetachBootVolume(ctx context.Context, cred Credentials, region, attachmentID string) error {
	err := c.Client.DetachBootVolume(ctx, cred, region, attachmentID)
	c.bust(err, cred)
	return err
}

func (c *CachedClient) ReplaceBootVolume(ctx context.Context, cred Credentials, region, instanceID, newBootVolumeID string) (BootVolumeAttachment, error) {
	out, err := c.Client.ReplaceBootVolume(ctx, cred, region, instanceID, newBootVolumeID)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) AttachVolume(ctx context.Context, cred Credentials, region, instanceID, volumeID string, readOnly bool) (VolumeAttachment, error) {
	out, err := c.Client.AttachVolume(ctx, cred, region, instanceID, volumeID, readOnly)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) DetachVolume(ctx context.Context, cred Credentials, region, attachmentID string) error {
	err := c.Client.DetachVolume(ctx, cred, region, attachmentID)
	c.bust(err, cred)
	return err
}

func (c *CachedClient) AttachVnic(ctx context.Context, cred Credentials, region, instanceID string, in AttachVnicInput) (Vnic, error) {
	out, err := c.Client.AttachVnic(ctx, cred, region, instanceID, in)
	c.bust(err, cred)
	return out, err
}

func (c *CachedClient) DetachVnic(ctx context.Context, cred Credentials, region, attachmentID string) error {
	err := c.Client.DetachVnic(ctx, cred, region, attachmentID)
	c.bust(err, cred)
	return err
}

func (c *CachedClient) AddVnicIpv6(ctx context.Context, cred Credentials, region, vnicID, address string) (string, error) {
	out, err := c.Client.AddVnicIpv6(ctx, cred, region, vnicID, address)
	c.bust(err, cred)
	return out, err
}
