package service

import (
	"context"
	"fmt"
	"strings"
	"time"

	"gorm.io/gorm"

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

// OciConfigService 管理 API Key 配置的导入、测活与快照同步。
type OciConfigService struct {
	db           *gorm.DB
	cipher       *crypto.Cipher
	client       oci.Client
	cleanupTasks *TaskService
	// auditRaw 按 configId:eventId 暂存审计原始事件(TTL 10 分钟),
	// 列表响应剥离 raw 后详情接口据此秒开;miss 走小窗重查兜底。
	auditRaw *cache.Cache
}

// NewOciConfigService 组装依赖。
func NewOciConfigService(db *gorm.DB, cipher *crypto.Cipher, client oci.Client) *OciConfigService {
	return &OciConfigService{db: db, cipher: cipher, client: client, auditRaw: cache.New(auditRawMax)}
}

// SetTenantCleanupDeps 注入租户删除提交后的任务内存状态同步依赖。
func (s *OciConfigService) SetTenantCleanupDeps(tasks *TaskService) {
	s.cleanupTasks = tasks
}

// ImportInput 是导入一份 API Key 的输入：
// ConfigINI 与显式字段二选一（ConfigINI 优先），私钥内容必填。
type ImportInput struct {
	Alias       string
	Group       string
	ConfigINI   string
	TenancyOCID string
	UserOCID    string
	Region      string
	Fingerprint string
	PrivateKey  string
	Passphrase  string
	// 多区域 / 多区间支持：开启后订阅区域与 compartment 入库缓存
	MultiRegion      bool
	MultiCompartment bool
	// ProxyID 关联出站代理,nil 直连
	ProxyID *uint
}

// Changes 记录一次测活前后云端信息字段的变化，值为 [旧值, 新值]。
type Changes map[string][2]string

// Import 保存一份新的 API Key 配置，随后立即测活并获取账户类别。
// 测活失败不回滚导入，结果记录在快照的状态字段里。
func (s *OciConfigService) Import(ctx context.Context, in ImportInput) (*model.OciConfig, error) {
	cred, err := s.buildCredentials(in)
	if err != nil {
		return nil, err
	}
	cfg, err := s.storeConfig(in.Alias, cred)
	if err != nil {
		return nil, err
	}
	cfg.Group = in.Group
	cfg.MultiRegion = in.MultiRegion
	cfg.MultiCompartment = in.MultiCompartment
	cfg.ProxyID = in.ProxyID
	if err := s.refresh(ctx, cfg, cred); err != nil {
		return nil, err
	}
	return cfg, nil
}

// Verify 重新测活并拉取云端信息，返回更新后的快照和字段变更集。
func (s *OciConfigService) Verify(ctx context.Context, id uint) (*model.OciConfig, Changes, error) {
	var cfg model.OciConfig
	if err := s.db.First(&cfg, id).Error; err != nil {
		return nil, nil, fmt.Errorf("find oci config %d: %w", id, err)
	}
	cred, err := s.credentialsOf(&cfg)
	if err != nil {
		return nil, nil, err
	}
	before := cfg
	if err := s.refresh(ctx, &cfg, cred); err != nil {
		return nil, nil, err
	}
	return &cfg, diffSnapshots(before, cfg), nil
}

// UpdateInput 是修改配置的输入；空字段保持不变。
// 替换私钥时必须同时提供新指纹；Passphrase 非 nil 时整体覆盖（空串表示清除口令）。
// Group 非 nil 时整体覆盖（空串表示取消分组）。
// MultiRegion / MultiCompartment 非 nil 时切换开关，开启后随本次测活同步缓存。
type UpdateInput struct {
	Alias            string
	Group            *string
	Region           string
	Fingerprint      string
	PrivateKey       string
	Passphrase       *string
	MultiRegion      *bool
	MultiCompartment *bool
	// ProxyID 非 nil 时切换关联:0 表示解除代理,>0 表示关联到该代理
	ProxyID *uint
}

// Update 修改配置的别名 / 默认区域 / 凭据字段，随后立即重新测活。
func (s *OciConfigService) Update(ctx context.Context, id uint, in UpdateInput) (*model.OciConfig, Changes, error) {
	if in.PrivateKey != "" && in.Fingerprint == "" {
		return nil, nil, fmt.Errorf("update oci config: fingerprint is required when replacing private key")
	}
	var cfg model.OciConfig
	if err := s.db.WithContext(ctx).First(&cfg, id).Error; err != nil {
		return nil, nil, fmt.Errorf("find oci config %d: %w", id, err)
	}
	if err := s.applyUpdate(&cfg, in); err != nil {
		return nil, nil, err
	}
	cred, err := s.credentialsOf(&cfg)
	if err != nil {
		return nil, nil, err
	}
	before := cfg
	if err := s.refresh(ctx, &cfg, cred); err != nil {
		return nil, nil, err
	}
	return &cfg, diffSnapshots(before, cfg), nil
}

// applyUpdate 把非空输入写进快照字段，凭据字段交由 applyCredentialUpdate 加密。
func (s *OciConfigService) applyUpdate(cfg *model.OciConfig, in UpdateInput) error {
	if in.Alias != "" {
		cfg.Alias = in.Alias
	}
	if in.Group != nil {
		cfg.Group = *in.Group
	}
	if in.Region != "" {
		cfg.Region = in.Region
	}
	if in.Fingerprint != "" {
		cfg.Fingerprint = in.Fingerprint
	}
	if in.MultiRegion != nil {
		cfg.MultiRegion = *in.MultiRegion
	}
	if in.MultiCompartment != nil {
		cfg.MultiCompartment = *in.MultiCompartment
	}
	if in.ProxyID != nil {
		if *in.ProxyID == 0 {
			cfg.ProxyID = nil
		} else {
			cfg.ProxyID = in.ProxyID
		}
	}
	return s.applyCredentialUpdate(cfg, in)
}

// applyCredentialUpdate 重新加密私钥与口令；Passphrase 非 nil 时整体覆盖。
func (s *OciConfigService) applyCredentialUpdate(cfg *model.OciConfig, in UpdateInput) error {
	if in.PrivateKey != "" {
		enc, err := s.cipher.EncryptString(in.PrivateKey)
		if err != nil {
			return fmt.Errorf("encrypt private key: %w", err)
		}
		cfg.PrivateKeyEnc = enc
	}
	if in.Passphrase == nil {
		return nil
	}
	cfg.PassphraseEnc = ""
	if *in.Passphrase != "" {
		enc, err := s.cipher.EncryptString(*in.Passphrase)
		if err != nil {
			return fmt.Errorf("encrypt passphrase: %w", err)
		}
		cfg.PassphraseEnc = enc
	}
	return nil
}

// ConfigSummary 是列表接口的瘦身视图：只含列表页与全局作用域选择器
// 消费的字段，订阅 / 促销 / 凭据元数据等详情字段走 Get 单查。
type ConfigSummary struct {
	ID               uint       `json:"id"`
	Alias            string     `json:"alias"`
	Group            string     `json:"group"`
	TenancyOCID      string     `json:"tenancyOcid"`
	TenancyName      string     `json:"tenancyName"`
	Region           string     `json:"region"`
	AccountType      string     `json:"accountType"`
	AliveStatus      string     `json:"aliveStatus"`
	LastError        string     `json:"lastError"`
	LastVerifiedAt   *time.Time `json:"lastVerifiedAt"`
	MultiRegion      bool       `json:"multiRegion"`
	MultiCompartment bool       `json:"multiCompartment"`
	ProxyID          *uint      `json:"proxyId"`
	// ProxyName 由 List 按关联填充,供表格代理列 hover 展示
	ProxyName string    `json:"proxyName"`
	CreatedAt time.Time `json:"createdAt"`
}

func toConfigSummary(cfg model.OciConfig) ConfigSummary {
	return ConfigSummary{
		ID:               cfg.ID,
		Alias:            cfg.Alias,
		Group:            cfg.Group,
		TenancyOCID:      cfg.TenancyOCID,
		TenancyName:      cfg.TenancyName,
		Region:           cfg.Region,
		AccountType:      cfg.AccountType,
		AliveStatus:      cfg.AliveStatus,
		LastError:        cfg.LastError,
		LastVerifiedAt:   cfg.LastVerifiedAt,
		MultiRegion:      cfg.MultiRegion,
		MultiCompartment: cfg.MultiCompartment,
		ProxyID:          cfg.ProxyID,
		CreatedAt:        cfg.CreatedAt,
	}
}

// List 返回全部配置的列表摘要。
func (s *OciConfigService) List(ctx context.Context) ([]ConfigSummary, error) {
	items := make([]model.OciConfig, 0)
	if err := s.db.WithContext(ctx).Order("id").Find(&items).Error; err != nil {
		return nil, fmt.Errorf("list oci configs: %w", err)
	}
	names, err := s.proxyNames(ctx)
	if err != nil {
		return nil, err
	}
	summaries := make([]ConfigSummary, len(items))
	for i, cfg := range items {
		summaries[i] = toConfigSummary(cfg)
		if cfg.ProxyID != nil {
			summaries[i].ProxyName = names[*cfg.ProxyID]
		}
	}
	return summaries, nil
}

// proxyNames 一次取回代理 id → 名称映射,避免列表逐行查询。
func (s *OciConfigService) proxyNames(ctx context.Context) (map[uint]string, error) {
	rows := []model.Proxy{}
	if err := s.db.WithContext(ctx).Select("id", "name").Find(&rows).Error; err != nil {
		return nil, fmt.Errorf("list proxy names: %w", err)
	}
	out := make(map[uint]string, len(rows))
	for _, r := range rows {
		out[r.ID] = r.Name
	}
	return out, nil
}

// Get 返回单个配置快照。
func (s *OciConfigService) Get(ctx context.Context, id uint) (*model.OciConfig, error) {
	var cfg model.OciConfig
	if err := s.db.WithContext(ctx).First(&cfg, id).Error; err != nil {
		return nil, fmt.Errorf("find oci config %d: %w", id, err)
	}
	return &cfg, nil
}

// Delete 在单一事务中删除配置及全部租户级本地关联数据。
func (s *OciConfigService) Delete(ctx context.Context, id uint) error {
	unlock := func() {}
	if s.cleanupTasks != nil {
		unlock = s.cleanupTasks.lockTenantCleanup()
	}
	defer unlock()
	result, err := s.deleteTenant(ctx, id)
	if err != nil {
		return err
	}
	s.afterTenantDelete(ctx, result)
	return nil
}

// refresh 执行测活与账户信息拉取，并把最新云端状态同步进快照。
// OCI 调用失败体现在快照状态字段，只有本地存储失败才返回 error。
func (s *OciConfigService) refresh(ctx context.Context, cfg *model.OciConfig, cred oci.Credentials) error {
	now := time.Now()
	cfg.LastVerifiedAt = &now
	info, err := s.client.ValidateKey(ctx, cred)
	if err != nil {
		cfg.AliveStatus = model.AliveStatusDead
		cfg.LastError = oci.CompactError(err)
	} else {
		cfg.AliveStatus = model.AliveStatusAlive
		cfg.LastError = ""
		cfg.TenancyName = info.Name
		cfg.HomeRegionKey = info.HomeRegionKey
		s.applyProfile(ctx, cfg, cred)
		s.syncScopeCaches(ctx, cfg, cred)
	}
	s.applySuspension(ctx, cfg, cred)
	return s.saveSnapshot(ctx, cfg)
}

// saveSnapshot 只更新当前版本，避免在途测活把已删租户重新插入。
func (s *OciConfigService) saveSnapshot(ctx context.Context, cfg *model.OciConfig) error {
	res := s.db.WithContext(ctx).Model(&model.OciConfig{}).
		Where("id = ? AND updated_at = ?", cfg.ID, cfg.UpdatedAt).
		Select("*").Omit("id", "created_at").Updates(cfg)
	if res.Error != nil {
		return fmt.Errorf("save oci config snapshot: %w", res.Error)
	}
	if res.RowsAffected != 1 {
		return fmt.Errorf("save oci config snapshot: %w", gorm.ErrRecordNotFound)
	}
	return nil
}

// applySuspension 查询账户能力接口:云端标记暂停的租户覆盖测活结论为 suspended。
// 该接口对暂停/已终止租户仍可访问(常规 API 此时多被拒,会误判失联);查询失败不改变结论。
func (s *OciConfigService) applySuspension(ctx context.Context, cfg *model.OciConfig, cred oci.Credentials) {
	caps, err := s.client.GetAccountCapabilities(ctx, cred, homeRegionOf(cfg))
	if err != nil || !caps.Suspended {
		return
	}
	cfg.AliveStatus = model.AliveStatusSuspended
	cfg.LastError = strings.TrimSpace("账户已被云端暂停 " + capsStatusNote(caps))
}

// capsStatusNote 生成暂停详情备注(账户状态,如 terminated)。
func capsStatusNote(caps oci.AccountCapabilities) string {
	if caps.AccountStatus == "" {
		return ""
	}
	return "(account status: " + caps.AccountStatus + ")"
}

// applyProfile 拉取订阅信息；失败只把类别降级为 unknown，不影响测活结论。
func (s *OciConfigService) applyProfile(ctx context.Context, cfg *model.OciConfig, cred oci.Credentials) {
	profile, err := s.client.FetchAccountProfile(ctx, cred)
	if err != nil {
		cfg.AccountType = model.AccountTypeUnknown
		cfg.LastError = fmt.Sprintf("fetch account profile: %s", oci.CompactError(err))
		return
	}
	cfg.AccountType = profile.AccountType
	cfg.SubscriptionID = profile.SubscriptionID
	cfg.PaymentModel = profile.PaymentModel
	cfg.SubscriptionTier = profile.SubscriptionTier
	cfg.PromotionStatus = profile.PromotionStatus
	cfg.PromotionAmount = profile.PromotionAmount
	cfg.PromotionExpires = profile.PromotionExpires
}

func (s *OciConfigService) buildCredentials(in ImportInput) (oci.Credentials, error) {
	cred := oci.Credentials{
		TenancyOCID: in.TenancyOCID,
		UserOCID:    in.UserOCID,
		Region:      in.Region,
		Fingerprint: in.Fingerprint,
	}
	if in.ConfigINI != "" {
		parsed, err := oci.ParseConfigINI(in.ConfigINI)
		if err != nil {
			return oci.Credentials{}, err
		}
		cred = parsed
	}
	cred.PrivateKey = in.PrivateKey
	if cred.Passphrase == "" {
		cred.Passphrase = in.Passphrase
	}
	if err := cred.Validate(); err != nil {
		return oci.Credentials{}, err
	}
	return cred, nil
}

func (s *OciConfigService) storeConfig(alias string, cred oci.Credentials) (*model.OciConfig, error) {
	keyEnc, err := s.cipher.EncryptString(cred.PrivateKey)
	if err != nil {
		return nil, fmt.Errorf("encrypt private key: %w", err)
	}
	passEnc := ""
	if cred.Passphrase != "" {
		if passEnc, err = s.cipher.EncryptString(cred.Passphrase); err != nil {
			return nil, fmt.Errorf("encrypt passphrase: %w", err)
		}
	}
	cfg := &model.OciConfig{
		Alias:         alias,
		UserOCID:      cred.UserOCID,
		TenancyOCID:   cred.TenancyOCID,
		Fingerprint:   cred.Fingerprint,
		Region:        cred.Region,
		PrivateKeyEnc: keyEnc,
		PassphraseEnc: passEnc,
		AccountType:   model.AccountTypeUnknown,
		AliveStatus:   model.AliveStatusUnknown,
	}
	if err := s.db.Create(cfg).Error; err != nil {
		return nil, fmt.Errorf("create oci config: %w", err)
	}
	return cfg, nil
}

// credentialsOf 从快照解密出完整凭据。
func (s *OciConfigService) credentialsOf(cfg *model.OciConfig) (oci.Credentials, error) {
	key, err := s.cipher.DecryptString(cfg.PrivateKeyEnc)
	if err != nil {
		return oci.Credentials{}, fmt.Errorf("decrypt private key of config %d: %w", cfg.ID, err)
	}
	pass := ""
	if cfg.PassphraseEnc != "" {
		if pass, err = s.cipher.DecryptString(cfg.PassphraseEnc); err != nil {
			return oci.Credentials{}, fmt.Errorf("decrypt passphrase of config %d: %w", cfg.ID, err)
		}
	}
	spec, err := s.proxySpecOf(cfg)
	if err != nil {
		return oci.Credentials{}, err
	}
	return oci.Credentials{
		TenancyOCID: cfg.TenancyOCID,
		UserOCID:    cfg.UserOCID,
		Region:      cfg.Region,
		Fingerprint: cfg.Fingerprint,
		PrivateKey:  key,
		Passphrase:  pass,
		Proxy:       spec,
	}, nil
}

// proxySpecOf 加载租户关联的出站代理;未关联返回 nil(直连)。
// 代理行缺失或解密失败时报错而非静默直连,避免期望走代理的流量泄露真实出口。
func (s *OciConfigService) proxySpecOf(cfg *model.OciConfig) (*oci.ProxySpec, error) {
	if cfg.ProxyID == nil {
		return nil, nil
	}
	var row model.Proxy
	if err := s.db.First(&row, *cfg.ProxyID).Error; err != nil {
		return nil, fmt.Errorf("find proxy %d of config %d: %w", *cfg.ProxyID, cfg.ID, err)
	}
	password := ""
	if row.PasswordEnc != "" {
		p, err := s.cipher.DecryptString(row.PasswordEnc)
		if err != nil {
			return nil, fmt.Errorf("decrypt proxy password: %w", err)
		}
		password = p
	}
	return &oci.ProxySpec{
		Type: row.Type, Host: row.Host, Port: row.Port,
		Username: row.Username, Password: password,
	}, nil
}

// diffSnapshots 找出两次快照间云端信息字段的变化。
func diffSnapshots(before, after model.OciConfig) Changes {
	fields := map[string][2]string{
		"aliveStatus":      {before.AliveStatus, after.AliveStatus},
		"tenancyName":      {before.TenancyName, after.TenancyName},
		"homeRegionKey":    {before.HomeRegionKey, after.HomeRegionKey},
		"accountType":      {before.AccountType, after.AccountType},
		"subscriptionId":   {before.SubscriptionID, after.SubscriptionID},
		"paymentModel":     {before.PaymentModel, after.PaymentModel},
		"subscriptionTier": {before.SubscriptionTier, after.SubscriptionTier},
		"promotionStatus":  {before.PromotionStatus, after.PromotionStatus},
	}
	changes := make(Changes)
	for name, pair := range fields {
		if pair[0] != pair[1] {
			changes[name] = pair
		}
	}
	return changes
}
