package host

import (
	"context"
	"database/sql"
	"strings"
	"sync"
	"time"

	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	hostservice "hyapp/services/user-service/internal/service/host"
)

type roleScopePolicyCacheEntry struct {
	policy    hostservice.RoleScopePolicy
	found     bool
	expiresAt time.Time
}

// GetRoleScopePolicy 通过 (app_code, scene) 主键读取 owner 配置。过期后数据库失败直接返回错误，不使用旧权限放宽结果。
func (r *Repository) GetRoleScopePolicy(ctx context.Context, scene string) (hostservice.RoleScopePolicy, bool, error) {
	if r == nil || r.db == nil {
		return hostservice.RoleScopePolicy{}, false, xerr.New(xerr.Unavailable, "host repository is not configured")
	}
	app := appcode.FromContext(ctx)
	cacheKey := app + "\x00" + scene
	now := time.Now()
	if cached, ok := r.cachedRoleScopePolicy(cacheKey, now); ok {
		return cached.policy, cached.found, nil
	}

	// 同一 App+scene 的并发首次读取只能落一次 MySQL，防止缓存过期瞬间形成配置表尖峰。
	keyLock := r.roleScopePolicyLock(cacheKey)
	keyLock.Lock()
	defer keyLock.Unlock()
	if cached, ok := r.cachedRoleScopePolicy(cacheKey, time.Now()); ok {
		return cached.policy, cached.found, nil
	}

	var policy hostservice.RoleScopePolicy
	err := r.db.QueryRowContext(ctx, `
		SELECT base_scope, expanded_scope, region_expansion_configurable, updated_at_ms
		FROM role_scope_policies
		WHERE app_code = ? AND scene = ?
		LIMIT 1
	`, app, scene).Scan(&policy.BaseScope, &policy.ExpandedScope, &policy.RegionExpansionConfigurable, &policy.UpdatedAtMS)
	if err != nil && err != sql.ErrNoRows {
		return hostservice.RoleScopePolicy{}, false, err
	}
	policy.Scene = scene
	entry := roleScopePolicyCacheEntry{
		policy:    policy,
		found:     err == nil,
		expiresAt: time.Now().Add(r.roleScopePolicyTTL()),
	}
	r.storeRoleScopePolicyCache(cacheKey, entry)
	return entry.policy, entry.found, nil
}

// UpsertRoleScopePolicies 在 user DB 单事务保存同一 App 的完整策略快照，提交后使当前实例缓存立即失效。
func (r *Repository) UpsertRoleScopePolicies(ctx context.Context, policies []hostservice.RoleScopePolicy, updatedAtMS int64) ([]hostservice.RoleScopePolicy, error) {
	if r == nil || r.db == nil {
		return nil, xerr.New(xerr.Unavailable, "host repository is not configured")
	}
	app := appcode.FromContext(ctx)
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	stored := make([]hostservice.RoleScopePolicy, 0, len(policies))
	for _, policy := range policies {
		if _, err := tx.ExecContext(ctx, `
			INSERT INTO role_scope_policies (
			  app_code, scene, base_scope, expanded_scope,
			  region_expansion_configurable, created_at_ms, updated_at_ms
			) VALUES (?, ?, ?, ?, ?, ?, ?)
			ON DUPLICATE KEY UPDATE
			  base_scope = VALUES(base_scope),
			  expanded_scope = VALUES(expanded_scope),
			  region_expansion_configurable = VALUES(region_expansion_configurable),
			  updated_at_ms = VALUES(updated_at_ms)
		`, app, policy.Scene, policy.BaseScope, policy.ExpandedScope, policy.RegionExpansionConfigurable, updatedAtMS, updatedAtMS); err != nil {
			return nil, err
		}
		policy.UpdatedAtMS = updatedAtMS
		stored = append(stored, policy)
	}
	if err := tx.Commit(); err != nil {
		return nil, err
	}
	r.invalidateRoleScopePolicies(app)
	return stored, nil
}

// roleRegionMatchRequired 把组织关系写入统一解释为 owner 策略；读取失败时调用方必须终止事务而不是放宽区域。
func (r *Repository) roleRegionMatchRequired(ctx context.Context) (bool, error) {
	policy, err := hostservice.ResolveRoleScopePolicy(ctx, r, hostservice.RoleScopeSceneOrganizationExpansion)
	if err != nil {
		return true, err
	}
	return policy.BaseScope != hostservice.RoleScopeGlobal, nil
}

func (r *Repository) cachedRoleScopePolicy(key string, now time.Time) (roleScopePolicyCacheEntry, bool) {
	r.roleScopeMu.Lock()
	defer r.roleScopeMu.Unlock()
	entry, ok := r.roleScopeCache[key]
	return entry, ok && now.Before(entry.expiresAt)
}

func (r *Repository) storeRoleScopePolicyCache(key string, entry roleScopePolicyCacheEntry) {
	r.roleScopeMu.Lock()
	defer r.roleScopeMu.Unlock()
	if r.roleScopeCache == nil {
		r.roleScopeCache = make(map[string]roleScopePolicyCacheEntry)
	}
	r.roleScopeCache[key] = entry
}

func (r *Repository) roleScopePolicyLock(key string) *sync.Mutex {
	r.roleScopeMu.Lock()
	defer r.roleScopeMu.Unlock()
	if r.roleScopeLocks == nil {
		r.roleScopeLocks = make(map[string]*sync.Mutex)
	}
	if lock := r.roleScopeLocks[key]; lock != nil {
		return lock
	}
	lock := &sync.Mutex{}
	r.roleScopeLocks[key] = lock
	return lock
}

func (r *Repository) roleScopePolicyTTL() time.Duration {
	r.roleScopeMu.Lock()
	defer r.roleScopeMu.Unlock()
	if r.roleScopeTTL <= 0 {
		return 30 * time.Second
	}
	return r.roleScopeTTL
}

func (r *Repository) invalidateRoleScopePolicies(app string) {
	prefix := app + "\x00"
	r.roleScopeMu.Lock()
	defer r.roleScopeMu.Unlock()
	for key := range r.roleScopeCache {
		if strings.HasPrefix(key, prefix) {
			delete(r.roleScopeCache, key)
		}
	}
}
