package dingtalk

import (
	"context"
	"sync"
	"time"
)

// 钉钉 access_token 缓存相关常量
const (
	// tokenRefreshBuffer token 过期前提前刷新的时间余量
	tokenRefreshBuffer = 2 * time.Minute
	// tokenMaxTTL 兜底 TTL（钉钉 token 有效期一般 7200s）
	tokenMaxTTL = 2 * time.Hour
)

// TokenStorage 可插拔的外部 Token 存储（如 Redis），接口保持最小
// 实现需保证并发安全。返回的 token 为空串视为"未命中/已过期"。
type TokenStorage interface {
	GetToken(ctx context.Context, clientID string) (token string, err error)
	SetToken(ctx context.Context, clientID string, token string, ttl time.Duration) error
}

// tokenEntry 内存 L1 缓存单元
type tokenEntry struct {
	token     string
	expiresAt time.Time
}

// tokenManager Token 管理器：L1 内存 + L2 可插拔（如 Redis），并带 singleflight 去重
type tokenManager struct {
	mu         sync.RWMutex
	entries    map[string]tokenEntry // key: clientID
	inflight   map[string]*inflightCall
	inflightMu sync.Mutex
	storage    TokenStorage // 可为 nil
}

type inflightCall struct {
	done  chan struct{}
	token string
	err   error
}

var (
	globalTokenManager = &tokenManager{
		entries:  make(map[string]tokenEntry),
		inflight: make(map[string]*inflightCall),
	}
	tokenStorageMu sync.RWMutex
)

// SetTokenStorage 注入外部 Token 存储（如 Redis），通常在 boot 阶段调用一次
func SetTokenStorage(storage TokenStorage) {
	tokenStorageMu.Lock()
	defer tokenStorageMu.Unlock()
	globalTokenManager.storage = storage
}

// getStorage 安全读取当前 TokenStorage
func (m *tokenManager) getStorage() TokenStorage {
	tokenStorageMu.RLock()
	defer tokenStorageMu.RUnlock()
	return m.storage
}

// getFromMem 从 L1 读取有效 token
func (m *tokenManager) getFromMem(clientID string) (string, bool) {
	m.mu.RLock()
	defer m.mu.RUnlock()
	e, ok := m.entries[clientID]
	if !ok {
		return "", false
	}
	if time.Now().After(e.expiresAt) {
		return "", false
	}
	return e.token, true
}

// setToMem 写入 L1
func (m *tokenManager) setToMem(clientID, token string, ttl time.Duration) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.entries[clientID] = tokenEntry{
		token:     token,
		expiresAt: time.Now().Add(ttl),
	}
}

// Fetch 获取 access_token：按 L1 → L2(storage) → refresh 顺序查找，带 singleflight
// refreshFn 由调用方（dingtalk.Client）注入，返回 (token, ttl, err)
func (m *tokenManager) Fetch(
	ctx context.Context,
	clientID string,
	refreshFn func(ctx context.Context) (string, time.Duration, error),
) (string, error) {
	// L1
	if tk, ok := m.getFromMem(clientID); ok {
		return tk, nil
	}

	// L2
	if storage := m.getStorage(); storage != nil {
		if tk, err := storage.GetToken(ctx, clientID); err == nil && tk != "" {
			// 回填 L1（保守给一个最大 TTL，由 L2 过期决定真实生命周期）
			m.setToMem(clientID, tk, tokenMaxTTL-tokenRefreshBuffer)
			return tk, nil
		}
	}

	// Refresh (singleflight)
	m.inflightMu.Lock()
	call, exists := m.inflight[clientID]
	if !exists {
		call = &inflightCall{done: make(chan struct{})}
		m.inflight[clientID] = call
		m.inflightMu.Unlock()

		go func() {
			defer func() {
				m.inflightMu.Lock()
				delete(m.inflight, clientID)
				m.inflightMu.Unlock()
				close(call.done)
			}()

			token, ttl, err := refreshFn(ctx)
			if err != nil {
				call.err = err
				return
			}

			effectiveTTL := ttl - tokenRefreshBuffer
			if effectiveTTL <= 0 {
				effectiveTTL = tokenMaxTTL - tokenRefreshBuffer
			}

			m.setToMem(clientID, token, effectiveTTL)
			if storage := m.getStorage(); storage != nil {
				_ = storage.SetToken(ctx, clientID, token, effectiveTTL)
			}
			call.token = token
		}()
	} else {
		m.inflightMu.Unlock()
	}

	select {
	case <-call.done:
		return call.token, call.err
	case <-ctx.Done():
		return "", ctx.Err()
	}
}

// Invalidate 主动失效某个 clientID 的缓存（例如收到 40014 InvalidAuthentication 时）
func (m *tokenManager) Invalidate(ctx context.Context, clientID string) {
	m.mu.Lock()
	delete(m.entries, clientID)
	m.mu.Unlock()
	if storage := m.getStorage(); storage != nil {
		_ = storage.SetToken(ctx, clientID, "", time.Second)
	}
}
