package httpapi

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strconv"
	"strings"
	"sync"
	"time"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/auth"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/identity"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

type identityConfigurationView struct {
	Active   *identity.Revision        `json:"active"`
	Draft    *identity.Revision        `json:"draft"`
	Previous *identity.Revision        `json:"previous"`
	Pairing  *identity.PairingExchange `json:"pairing,omitempty"`
	Runtime  identityRuntimeStatus     `json:"runtime"`
}

type identityRuntimeStatus struct {
	Enabled            bool   `json:"enabled"`
	RevisionID         string `json:"revisionId,omitempty"`
	Login              string `json:"login"`
	JIT                string `json:"jit"`
	TokenIntrospection string `json:"tokenIntrospection"`
	SessionRevocation  string `json:"sessionRevocation"`
	LastTraceID        string `json:"lastTraceId,omitempty"`
	LastAuditID        string `json:"lastAuditId,omitempty"`
	LastErrorCategory  string `json:"lastErrorCategory,omitempty"`
}

type publicIdentityConfiguration struct {
	Enabled      bool     `json:"enabled"`
	OIDCLogin    bool     `json:"oidcLogin"`
	LoginURL     string   `json:"loginUrl,omitempty"`
	LogoutURL    string   `json:"logoutUrl,omitempty"`
	ContextTypes []string `json:"contextTypes,omitempty"`
	Status       string   `json:"status"`
}

type identityPolicyPatch struct {
	LocalTenantKey         *string `json:"localTenantKey"`
	RolePrefix             *string `json:"rolePrefix"`
	JITEnabled             *bool   `json:"jitEnabled"`
	LegacyJWTEnabled       *bool   `json:"legacyJwtEnabled"`
	SessionIdleSeconds     *int    `json:"sessionIdleSeconds"`
	SessionAbsoluteSeconds *int    `json:"sessionAbsoluteSeconds"`
	SessionRefreshSeconds  *int    `json:"sessionRefreshSeconds"`
}

type storedIdentityResponse struct {
	Status  int             `json:"status"`
	Body    json.RawMessage `json:"body"`
	ETag    string          `json:"etag,omitempty"`
	AuditID string          `json:"auditId,omitempty"`
}

type identityWriteOperation struct {
	operation, key, requestHash string
	release                     func()
	once                        sync.Once
}

type identityPairingWorker struct {
	cancel context.CancelFunc
	done   chan struct{}
}

func (operation *identityWriteOperation) close() {
	if operation != nil {
		operation.once.Do(operation.release)
	}
}

// getPublicIdentityConfiguration godoc
// @Summary 获取公开统一认证状态
// @Description 返回 Web 运行时需要的启用状态和非敏感登录、退出入口，不返回 Client、Secret 或内部标识。
// @Tags identity
// @Produce json
// @Success 200 {object} publicIdentityConfiguration
// @Router /api/v1/public/identity [get]
func (s *Server) getPublicIdentityConfiguration(w http.ResponseWriter, _ *http.Request) {
	w.Header().Set("Cache-Control", "no-store")
	runtime := s.currentIdentityRuntime()
	if runtime == nil || runtime.Revision.State != identity.RevisionActive {
		writeJSON(w, http.StatusOK, publicIdentityConfiguration{Status: "disabled"})
		return
	}
	view := publicIdentityConfiguration{Enabled: true, Status: "active"}
	if oidcRuntimeReady(runtime) {
		view.OIDCLogin = true
		view.LoginURL = "/api/v1/auth/oidc/login"
		view.LogoutURL = "/api/v1/auth/oidc/logout"
		view.ContextTypes = []string{"tenant"}
		if runtime.Revision.TenantMode == "multi_tenant" {
			view.ContextTypes = []string{"platform", "tenant"}
		}
	}
	writeJSON(w, http.StatusOK, view)
}

// getIdentityConfiguration godoc
// @Summary 获取统一认证配置
// @Description 返回 Active、Draft、Previous Revision、配对进度和脱敏运行时健康状态。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Success 200 {object} identityConfigurationView
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 503 {object} ErrorEnvelope
// @Router /api/admin/system/identity/configuration [get]
func (s *Server) getIdentityConfiguration(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Cache-Control", "no-store")
	view := identityConfigurationView{Runtime: s.identityRuntimeStatus(r)}
	if revision, err := s.store.ActiveIdentityConfigurationRevision(r.Context()); err == nil {
		view.Active = &revision
	} else if !errors.Is(err, identity.ErrRevisionNotFound) {
		writeError(w, http.StatusServiceUnavailable, "统一认证配置暂时不可用", "IDENTITY_CONFIGURATION_UNAVAILABLE")
		return
	}
	if revision, err := s.store.LatestInactiveIdentityConfigurationRevision(r.Context()); err == nil {
		view.Draft = &revision
		if pairing, pairingErr := s.store.IdentityPairingExchangeForRevision(r.Context(), revision.ID); pairingErr == nil {
			view.Pairing = &pairing
		}
	} else if !errors.Is(err, identity.ErrRevisionNotFound) {
		writeError(w, http.StatusServiceUnavailable, "统一认证草稿暂时不可用", "IDENTITY_CONFIGURATION_UNAVAILABLE")
		return
	}
	if revision, err := s.store.LatestSupersededIdentityConfigurationRevision(r.Context()); err == nil {
		view.Previous = &revision
	} else if !errors.Is(err, identity.ErrRevisionNotFound) {
		writeError(w, http.StatusServiceUnavailable, "统一认证历史版本暂时不可用", "IDENTITY_CONFIGURATION_UNAVAILABLE")
		return
	}
	writeJSON(w, http.StatusOK, view)
}

// startIdentityPairing godoc
// @Summary 启动统一认证配对
// @Description 使用一次性接入码创建 Draft 和异步 Exchange。接入码仅允许出现在请求 Body。
// @Tags identity
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param Idempotency-Key header string true "幂等键"
// @Param If-Match header string true "首次配对固定为 W/\"0\""
// @Param body body identity.PairingInput true "配对参数"
// @Success 202 {object} identity.PairingExchange
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 428 {object} ErrorEnvelope
// @Router /api/admin/system/identity/pairings [post]
func (s *Server) startIdentityPairing(w http.ResponseWriter, r *http.Request) {
	var input identity.PairingInput
	if !decodeIdentityRequest(w, r, &input) {
		return
	}
	operation, ok := s.beginIdentityWrite(w, r, "pairing.start", 0, input)
	if !ok {
		return
	}
	defer operation.close()
	traceID := ensureIdentityTraceID(w, r)
	auditID, ok := s.requireIdentityConfigurationAudit(w, r, "pairing.start", "pending", traceID)
	if !ok {
		return
	}
	pairing, err := s.identityPairing.Start(r.Context(), input, traceID)
	if err != nil {
		s.writeIdentityError(w, r, "pairing.start", "", traceID, err)
		return
	}
	pairing.AuthCenterAuditID = ""
	s.completeIdentityWrite(w, r, operation, http.StatusAccepted, pairing, pairing.Version, auditID)
	s.startIdentityPairingWorker(pairing.ID)
}

// getIdentityPairing godoc
// @Summary 查询统一认证配对状态
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Param pairingID path string true "配对 ID"
// @Success 200 {object} identity.PairingExchange
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Router /api/admin/system/identity/pairings/{pairingID} [get]
func (s *Server) getIdentityPairing(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Cache-Control", "no-store")
	pairing, err := s.store.IdentityPairingExchange(r.Context(), r.PathValue("pairingID"))
	if err != nil {
		s.writeIdentityError(w, r, "pairing.get", r.PathValue("pairingID"), ensureIdentityTraceID(w, r), err)
		return
	}
	w.Header().Set("ETag", identityETag(pairing.Version))
	writeJSON(w, http.StatusOK, pairing)
}

// cancelIdentityPairing godoc
// @Summary 放弃本地统一认证配对
// @Description 原子封存未激活 Revision，并异步清理由该 Revision 拥有的临时 Secret 与 SSF 连接。不会撤销已经完成的远端 Exchange；下一次凭据交付会轮换机器凭据。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Param pairingID path string true "配对 ID"
// @Param Idempotency-Key header string true "幂等键"
// @Param If-Match header string true "当前 Pairing ETag"
// @Success 202 {object} identity.PairingExchange
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 412 {object} ErrorEnvelope
// @Failure 428 {object} ErrorEnvelope
// @Router /api/admin/system/identity/pairings/{pairingID}/cancel [post]
func (s *Server) cancelIdentityPairing(w http.ResponseWriter, r *http.Request) {
	expectedVersion, ok := requiredIdentityVersion(w, r)
	if !ok {
		return
	}
	pairingID := r.PathValue("pairingID")
	operation, ok := s.beginIdentityWriteWithVersion(w, r, "pairing.cancel", expectedVersion, struct {
		PairingID string `json:"pairingId"`
	}{PairingID: pairingID})
	if !ok {
		return
	}
	defer operation.close()
	traceID := ensureIdentityTraceID(w, r)
	auditID, ok := s.requireIdentityConfigurationAudit(w, r, "pairing.cancel", pairingID, traceID)
	if !ok {
		return
	}
	pairing, err := s.identityPairing.Cancel(r.Context(), pairingID, expectedVersion, traceID, auditID)
	if err != nil {
		s.writeIdentityError(w, r, "pairing.cancel", pairingID, traceID, err)
		return
	}
	done := s.stopIdentityPairingWorker(pairing.ID)
	s.completeIdentityWrite(w, r, operation, http.StatusAccepted, pairing, pairing.Version, auditID)
	s.startIdentityPairingCleanupWorker(pairing.ID, done)
}

// retireIdentityPairingSecurityEventConflict godoc
// @Summary 安全退役阻塞配对的旧 SSF 连接
// @Description 仅当指定 Pairing 仍因 owner 冲突停在 credentials_saved 时，退役不属于该 Revision 的旧连接；陈旧请求不会退役当前 Pairing 已创建的新连接。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Param pairingID path string true "配对 ID"
// @Param Idempotency-Key header string true "幂等键"
// @Param If-Match header string true "当前 Pairing ETag"
// @Success 202 {object} identity.PairingExchange
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 404 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 412 {object} ErrorEnvelope
// @Failure 428 {object} ErrorEnvelope
// @Router /api/admin/system/identity/pairings/{pairingID}/retire-conflicting-security-event [post]
func (s *Server) retireIdentityPairingSecurityEventConflict(w http.ResponseWriter, r *http.Request) {
	expectedVersion, ok := requiredIdentityVersion(w, r)
	if !ok {
		return
	}
	pairingID := r.PathValue("pairingID")
	operation, ok := s.beginIdentityWriteWithVersion(w, r, "pairing.retire_security_event_conflict", expectedVersion, struct {
		PairingID string `json:"pairingId"`
	}{PairingID: pairingID})
	if !ok {
		return
	}
	defer operation.close()
	traceID := ensureIdentityTraceID(w, r)
	auditID, ok := s.requireIdentityConfigurationAudit(w, r, "pairing.retire_security_event_conflict", pairingID, traceID)
	if !ok {
		return
	}
	pairing, err := s.identityPairing.RetireConflictingSecurityEvents(r.Context(), pairingID, expectedVersion)
	if err != nil {
		s.writeIdentityError(w, r, "pairing.retire_security_event_conflict", pairingID, traceID, err)
		return
	}
	s.completeIdentityWrite(w, r, operation, http.StatusAccepted, pairing, pairing.Version, auditID)
	s.startIdentityPairingWorker(pairing.ID)
}

// updateIdentityDraftPolicy godoc
// @Summary 修改统一认证 Draft 策略
// @Tags identity
// @Accept json
// @Produce json
// @Security BearerAuth
// @Param revisionID path string true "Revision ID"
// @Param Idempotency-Key header string true "幂等键"
// @Param If-Match header string true "当前 Revision ETag"
// @Param body body identityPolicyPatch true "Gateway 本地策略"
// @Success 200 {object} identity.Revision
// @Failure 400 {object} ErrorEnvelope
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 412 {object} ErrorEnvelope
// @Failure 428 {object} ErrorEnvelope
// @Router /api/admin/system/identity/revisions/{revisionID} [patch]
func (s *Server) updateIdentityDraftPolicy(w http.ResponseWriter, r *http.Request) {
	var patch identityPolicyPatch
	if !decodeIdentityRequest(w, r, &patch) {
		return
	}
	expectedVersion, ok := requiredIdentityVersion(w, r)
	if !ok {
		return
	}
	operation, ok := s.beginIdentityWriteWithVersion(w, r, "revision.policy", expectedVersion, patch)
	if !ok {
		return
	}
	defer operation.close()
	revision, err := s.store.IdentityConfigurationRevision(r.Context(), r.PathValue("revisionID"))
	if err != nil || revision.Version != expectedVersion {
		s.writeIdentityError(w, r, "revision.policy", r.PathValue("revisionID"), ensureIdentityTraceID(w, r), firstIdentityError(err, identity.ErrRevisionConflict))
		return
	}
	policy := identity.RevisionPolicy{
		TenantMode: revision.TenantMode, LocalTenantKey: revision.LocalTenantKey, RolePrefix: revision.RolePrefix, JITEnabled: revision.JITEnabled,
		LegacyJWTEnabled: revision.LegacyJWTEnabled, SessionIdleSeconds: revision.SessionIdleSeconds,
		SessionAbsoluteSeconds: revision.SessionAbsoluteSeconds, SessionRefreshSeconds: revision.SessionRefreshSeconds,
	}
	applyIdentityPolicyPatch(&policy, patch)
	traceID := ensureIdentityTraceID(w, r)
	auditID, ok := s.requireIdentityConfigurationAudit(w, r, "revision.policy", revision.ID, traceID)
	if !ok {
		return
	}
	updated, err := s.store.UpdateIdentityRevisionPolicy(r.Context(), revision.ID, expectedVersion, policy)
	if err != nil {
		s.writeIdentityError(w, r, "revision.policy", revision.ID, traceID, err)
		return
	}
	s.completeIdentityWrite(w, r, operation, http.StatusOK, updated, updated.Version, auditID)
}

// validateIdentityRevision godoc
// @Summary 验证统一认证 Revision
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Param revisionID path string true "Revision ID"
// @Param Idempotency-Key header string true "幂等键"
// @Param If-Match header string true "当前 Revision ETag"
// @Success 200 {object} identity.Revision
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 412 {object} ErrorEnvelope
// @Failure 428 {object} ErrorEnvelope
// @Failure 502 {object} ErrorEnvelope
// @Router /api/admin/system/identity/revisions/{revisionID}/validate [post]
func (s *Server) validateIdentityRevision(w http.ResponseWriter, r *http.Request) {
	s.runIdentityRevisionAction(w, r, "revision.validate", func(expected int64, traceID, auditID string) (identity.Revision, error) {
		return s.identityRuntime.Validate(r.Context(), r.PathValue("revisionID"), expected, traceID, auditID)
	})
}

// activateIdentityRevision godoc
// @Summary 激活统一认证 Revision
// @Description 在完整候选 Runtime 验证成功且本地 Break-glass Manager 可用后原子热切换。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Param revisionID path string true "Revision ID"
// @Param Idempotency-Key header string true "幂等键"
// @Param If-Match header string true "当前 Revision ETag"
// @Success 200 {object} identity.Revision
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 412 {object} ErrorEnvelope
// @Failure 428 {object} ErrorEnvelope
// @Router /api/admin/system/identity/revisions/{revisionID}/activate [post]
func (s *Server) activateIdentityRevision(w http.ResponseWriter, r *http.Request) {
	s.runIdentityRevisionAction(w, r, "revision.activate", func(expected int64, traceID, auditID string) (identity.Revision, error) {
		return s.identityRuntime.Activate(r.Context(), r.PathValue("revisionID"), expected, traceID, auditID)
	})
}

// rollbackIdentityRevision godoc
// @Summary 请求恢复历史统一认证 Revision
// @Description 当前远端 OAuth/SSF 资源未版本化，接口会拒绝直接回滚并要求禁用后使用新接入码完成交接。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Param revisionID path string true "Revision ID"
// @Param Idempotency-Key header string true "幂等键"
// @Param If-Match header string true "当前 Revision ETag"
// @Success 200 {object} identity.Revision
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 412 {object} ErrorEnvelope
// @Failure 428 {object} ErrorEnvelope
// @Router /api/admin/system/identity/revisions/{revisionID}/rollback [post]
func (s *Server) rollbackIdentityRevision(w http.ResponseWriter, r *http.Request) {
	s.runIdentityRevisionAction(w, r, "revision.rollback", func(expected int64, traceID, auditID string) (identity.Revision, error) {
		return s.identityRuntime.Rollback(r.Context(), r.PathValue("revisionID"), expected, traceID, auditID)
	})
}

// disableIdentityConfiguration godoc
// @Summary 禁用统一认证
// @Description 将 Revision 转为只读 Superseded 审计历史，清理 BFF Session，并继续允许本地管理登录；重新接入必须使用新的接入码。
// @Tags identity
// @Produce json
// @Security BearerAuth
// @Param Idempotency-Key header string true "幂等键"
// @Param If-Match header string true "当前 Active Revision ETag"
// @Success 200 {object} identity.Revision
// @Failure 401 {object} ErrorEnvelope
// @Failure 403 {object} ErrorEnvelope
// @Failure 409 {object} ErrorEnvelope
// @Failure 412 {object} ErrorEnvelope
// @Failure 428 {object} ErrorEnvelope
// @Router /api/admin/system/identity/disable [post]
func (s *Server) disableIdentityConfiguration(w http.ResponseWriter, r *http.Request) {
	expectedVersion, ok := requiredIdentityVersion(w, r)
	if !ok {
		return
	}
	operation, ok := s.beginIdentityWriteWithVersion(w, r, "revision.disable", expectedVersion, struct{}{})
	if !ok {
		return
	}
	defer operation.close()
	traceID := ensureIdentityTraceID(w, r)
	auditID, ok := s.requireIdentityConfigurationAudit(w, r, "revision.disable", "active", traceID)
	if !ok {
		return
	}
	disabled, err := s.identityRuntime.Disable(r.Context(), expectedVersion, traceID, auditID)
	if err != nil {
		s.writeIdentityError(w, r, "revision.disable", "active", traceID, err)
		return
	}
	s.completeIdentityWrite(w, r, operation, http.StatusOK, disabled, disabled.Version, auditID)
}

func (s *Server) runIdentityRevisionAction(w http.ResponseWriter, r *http.Request, action string, run func(int64, string, string) (identity.Revision, error)) {
	expectedVersion, ok := requiredIdentityVersion(w, r)
	if !ok {
		return
	}
	operation, ok := s.beginIdentityWriteWithVersion(w, r, action, expectedVersion, struct{}{})
	if !ok {
		return
	}
	defer operation.close()
	traceID := ensureIdentityTraceID(w, r)
	auditID, ok := s.requireIdentityConfigurationAudit(w, r, action, r.PathValue("revisionID"), traceID)
	if !ok {
		return
	}
	revision, err := run(expectedVersion, traceID, auditID)
	if err != nil {
		s.writeIdentityError(w, r, action, r.PathValue("revisionID"), traceID, err)
		return
	}
	s.completeIdentityWrite(w, r, operation, http.StatusOK, revision, revision.Version, auditID)
}

func (s *Server) identityRuntimeStatus(r *http.Request) identityRuntimeStatus {
	runtime := s.currentIdentityRuntime()
	if runtime == nil {
		return identityRuntimeStatus{Login: "disabled", JIT: "disabled", TokenIntrospection: "disabled", SessionRevocation: "disabled"}
	}
	revision := runtime.Revision
	status := identityRuntimeStatus{
		Enabled: true, RevisionID: revision.ID, Login: capabilityHealth(runtime.PublicClient != nil),
		JIT: capabilityHealth(revision.JITEnabled), TokenIntrospection: capabilityHealth(revision.TokenIntrospection),
		SessionRevocation: capabilityHealth(revision.SessionRevocation), LastTraceID: revision.LastTraceID,
		LastAuditID: revision.LastAuditID, LastErrorCategory: revision.LastErrorCategory,
	}
	if revision.SessionRevocation && runtime.SecurityEvents != nil {
		if connection, err := runtime.SecurityEvents.Get(r.Context()); err == nil && connection.LifecycleStatus != "active" {
			status.SessionRevocation = connection.HealthMode
		}
	}
	return status
}

func capabilityHealth(enabled bool) string {
	if enabled {
		return "healthy"
	}
	return "disabled"
}

func decodeIdentityRequest(w http.ResponseWriter, r *http.Request, output any) bool {
	r.Body = http.MaxBytesReader(w, r.Body, 32*1024)
	decoder := json.NewDecoder(r.Body)
	decoder.DisallowUnknownFields()
	if err := decoder.Decode(output); err != nil {
		writeError(w, http.StatusBadRequest, "统一认证请求格式无效", "INVALID_REQUEST")
		return false
	}
	if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
		writeError(w, http.StatusBadRequest, "统一认证请求格式无效", "INVALID_REQUEST")
		return false
	}
	return true
}

func (s *Server) beginIdentityWrite(w http.ResponseWriter, r *http.Request, operation string, version int64, request any) (*identityWriteOperation, bool) {
	if parsed, ok := requiredIdentityVersion(w, r); !ok || parsed != version {
		if ok {
			writeError(w, http.StatusPreconditionFailed, "统一认证配置版本已变化", "IDENTITY_VERSION_CONFLICT")
		}
		return nil, false
	}
	return s.beginIdentityWriteWithVersion(w, r, operation, version, request)
}

func (s *Server) beginIdentityWriteWithVersion(w http.ResponseWriter, r *http.Request, operation string, version int64, request any) (*identityWriteOperation, bool) {
	key := strings.TrimSpace(r.Header.Get("Idempotency-Key"))
	if key == "" || len(key) > 255 {
		writeError(w, http.StatusBadRequest, "Idempotency-Key is required", "IDEMPOTENCY_KEY_REQUIRED")
		return nil, false
	}
	requestHash := identityRequestHash(operation, version, request)
	s.identityManagementMu.Lock()
	write := &identityWriteOperation{operation: operation, key: key, requestHash: requestHash, release: s.identityManagementMu.Unlock}
	if s.store == nil {
		return write, true
	}
	recorded, err := s.store.IdentityManagementRequest(r.Context(), operation, key)
	if errors.Is(err, store.ErrIdentityManagementRequestNotFound) {
		return write, true
	}
	if err != nil {
		write.close()
		writeError(w, http.StatusServiceUnavailable, "幂等状态暂时不可用", "IDENTITY_IDEMPOTENCY_UNAVAILABLE")
		return nil, false
	}
	if recorded.RequestHash != requestHash {
		write.close()
		writeError(w, http.StatusConflict, "Idempotency-Key 已用于其他请求", "IDEMPOTENCY_KEY_REUSED")
		return nil, false
	}
	var response storedIdentityResponse
	if json.Unmarshal(recorded.Response, &response) != nil {
		write.close()
		writeError(w, http.StatusServiceUnavailable, "幂等响应暂时不可用", "IDENTITY_IDEMPOTENCY_UNAVAILABLE")
		return nil, false
	}
	write.close()
	w.Header().Set("Cache-Control", "no-store")
	w.Header().Set("Idempotent-Replayed", "true")
	if response.ETag != "" {
		w.Header().Set("ETag", response.ETag)
	}
	if response.AuditID != "" {
		w.Header().Set("X-Audit-Id", response.AuditID)
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(response.Status)
	_, _ = w.Write(response.Body)
	return nil, false
}

func identityRequestHash(operation string, version int64, request any) string {
	encoded, _ := json.Marshal(request)
	digest := sha256.Sum256(append([]byte(fmt.Sprintf("%s\x00%d\x00", operation, version)), encoded...))
	return fmt.Sprintf("%x", digest[:])
}

func (s *Server) completeIdentityWrite(w http.ResponseWriter, r *http.Request, operation *identityWriteOperation, status int, payload any, version int64, auditID string) {
	body, _ := json.Marshal(payload)
	stored, _ := json.Marshal(storedIdentityResponse{Status: status, Body: body, ETag: identityETag(version), AuditID: auditID})
	if s.store != nil {
		if err := s.store.RecordIdentityManagementRequest(r.Context(), store.IdentityManagementRequest{
			Operation: operation.operation, Key: operation.key, RequestHash: operation.requestHash, Response: stored,
		}); err != nil && s.logger != nil {
			s.logger.ErrorContext(r.Context(), "identity idempotency response could not be recorded", "operation", operation.operation, "error_category", "idempotency_store_failed")
		}
	}
	operation.close()
	w.Header().Set("Cache-Control", "no-store")
	w.Header().Set("ETag", identityETag(version))
	if auditID != "" {
		w.Header().Set("X-Audit-Id", auditID)
	}
	writeJSON(w, status, payload)
}

func requiredIdentityVersion(w http.ResponseWriter, r *http.Request) (int64, bool) {
	value := strings.Trim(strings.TrimPrefix(strings.TrimSpace(r.Header.Get("If-Match")), "W/"), `"`)
	version, err := strconv.ParseInt(value, 10, 64)
	if err != nil || version < 0 {
		writeError(w, http.StatusPreconditionRequired, "If-Match is required", "IF_MATCH_REQUIRED")
		return 0, false
	}
	return version, true
}

func identityETag(version int64) string { return fmt.Sprintf(`W/"%d"`, version) }

func applyIdentityPolicyPatch(policy *identity.RevisionPolicy, patch identityPolicyPatch) {
	if patch.LocalTenantKey != nil {
		policy.LocalTenantKey = strings.TrimSpace(*patch.LocalTenantKey)
	}
	if patch.RolePrefix != nil {
		policy.RolePrefix = strings.TrimSpace(*patch.RolePrefix)
	}
	if patch.JITEnabled != nil {
		policy.JITEnabled = *patch.JITEnabled
	}
	if patch.LegacyJWTEnabled != nil {
		policy.LegacyJWTEnabled = *patch.LegacyJWTEnabled
	}
	if patch.SessionIdleSeconds != nil {
		policy.SessionIdleSeconds = *patch.SessionIdleSeconds
	}
	if patch.SessionAbsoluteSeconds != nil {
		policy.SessionAbsoluteSeconds = *patch.SessionAbsoluteSeconds
	}
	if patch.SessionRefreshSeconds != nil {
		policy.SessionRefreshSeconds = *patch.SessionRefreshSeconds
	}
}

func firstIdentityError(actual, fallback error) error {
	if actual != nil {
		return actual
	}
	return fallback
}

func ensureIdentityTraceID(w http.ResponseWriter, r *http.Request) string {
	return ensureSecurityEventTraceID(w, r)
}

func (s *Server) writeIdentityError(w http.ResponseWriter, r *http.Request, action, targetID, traceID string, err error) {
	status, message, code := identityErrorProjection(err)
	s.recordIdentityConfigurationAudit(r, action, targetID, "failure", traceID, code)
	writeError(w, status, message, code)
}

func identityErrorProjection(err error) (int, string, string) {
	var categorized interface{ SafeErrorCategory() string }
	safeCategory := ""
	if errors.As(err, &categorized) {
		safeCategory = categorized.SafeErrorCategory()
	}
	switch {
	case errors.Is(err, identity.ErrRevisionNotFound):
		return http.StatusNotFound, "统一认证配置不存在", "IDENTITY_CONFIGURATION_NOT_FOUND"
	case errors.Is(err, identity.ErrRevisionConflict):
		return http.StatusPreconditionFailed, "统一认证配置版本或状态已变化", "IDENTITY_VERSION_CONFLICT"
	case errors.Is(err, identity.ErrBreakGlassRequired):
		return http.StatusConflict, "请先保留至少一个可用的本地应急管理员凭据", "BREAK_GLASS_MANAGER_REQUIRED"
	case errors.Is(err, identity.ErrLocalTenantInvalid):
		return http.StatusConflict, "本地租户映射无效", "IDENTITY_LOCAL_TENANT_INVALID"
	case errors.Is(err, identity.ErrPairingInProgress):
		return http.StatusConflict, "请先完成或放弃当前统一认证配对", "IDENTITY_PAIRING_IN_PROGRESS"
	case errors.Is(err, identity.ErrActiveConfigurationHandoffRequired):
		return http.StatusConflict, "当前仍有 Active 统一认证配置；请先禁用，再使用新接入码配对", "IDENTITY_ACTIVE_CONFIGURATION_HANDOFF_REQUIRED"
	case errors.Is(err, identity.ErrRollbackConfigurationHandoffRequired):
		return http.StatusConflict, "旧版本关联的远端 OAuth/SSF 资源可能已变化；请禁用后使用新接入码恢复", "IDENTITY_ROLLBACK_CONFIGURATION_HANDOFF_REQUIRED"
	case errors.Is(err, identity.ErrSecurityEventRetirementPending):
		return http.StatusConflict, "旧安全事件 Stream 尚未完成断开；系统会继续重试，请稍后再次禁用", "IDENTITY_SECURITY_EVENT_RETIREMENT_PENDING"
	case errors.Is(err, identity.ErrPairingNotCancellable):
		return http.StatusConflict, "当前统一认证配置不能放弃", "IDENTITY_PAIRING_NOT_CANCELLABLE"
	case errors.Is(err, identity.ErrPairingConflictNotResolvable):
		return http.StatusConflict, "当前配对已不需要退役旧安全事件连接，请刷新状态", "IDENTITY_PAIRING_CONFLICT_NOT_RESOLVABLE"
	case safeCategory == "credential_handoff_unsafe":
		return http.StatusConflict, "旧安全事件连接与本次认证中心不匹配，系统已拒绝发送凭据；请先在原配置下断开旧连接", "IDENTITY_SECURITY_EVENT_HANDOFF_UNSAFE"
	case err != nil && (strings.Contains(err.Error(), "invalid") || strings.Contains(err.Error(), "required")):
		return http.StatusBadRequest, "统一认证配置无效", "IDENTITY_CONFIGURATION_INVALID"
	default:
		return http.StatusBadGateway, "认证中心或统一认证服务暂时不可用", "IDENTITY_SERVICE_UNAVAILABLE"
	}
}

func (s *Server) recordIdentityConfigurationAudit(r *http.Request, action, targetID, outcome, traceID, errorCategory string) string {
	if s.store == nil {
		return ""
	}
	actor, _ := auth.UserFromContext(r.Context())
	input := store.AuditLogInput{
		Category: "identity", Action: "identity." + action, TargetType: "identity_configuration_revision",
		TargetID: firstNonEmptyText(targetID, "pending"), RequestIP: limitAuditText(requestIP(r), 128),
		UserAgent: limitAuditText(r.UserAgent(), 512), Metadata: map[string]any{
			"outcome": outcome, "traceId": traceID, "errorCategory": errorCategory,
		},
	}
	if actor != nil {
		input.ActorGatewayUserID = uuidText(firstNonEmptyText(actor.GatewayUserID, actor.ID))
		input.ActorUserID, input.ActorUsername, input.ActorSource, input.ActorRoles = actor.ID, actor.Username, actor.Source, actor.Roles
	}
	audit, err := s.store.RecordAuditLog(r.Context(), input)
	if err != nil {
		if s.logger != nil {
			s.logger.WarnContext(r.Context(), "record identity audit failed", "action", action, "error_category", "audit_store_failed", "trace_id", traceID)
		}
		return ""
	}
	return audit.ID
}

func (s *Server) requireIdentityConfigurationAudit(w http.ResponseWriter, r *http.Request, action, targetID, traceID string) (string, bool) {
	auditID := s.recordIdentityConfigurationAudit(r, action, targetID, "requested", traceID, "")
	if auditID == "" {
		writeError(w, http.StatusServiceUnavailable, "统一认证审计暂时不可用，操作未执行", "IDENTITY_AUDIT_UNAVAILABLE")
		return "", false
	}
	w.Header().Set("X-Audit-Id", auditID)
	return auditID, true
}

func (s *Server) startIdentityPairingWorker(pairingID string) {
	workerContext, cancel := context.WithCancel(s.ctx)
	worker := &identityPairingWorker{cancel: cancel, done: make(chan struct{})}
	if _, loaded := s.identityPairingWorkers.LoadOrStore(pairingID, worker); loaded {
		cancel()
		return
	}
	go func() {
		defer func() {
			s.identityPairingWorkers.CompareAndDelete(pairingID, worker)
			close(worker.done)
		}()
		delay := time.Second
		for {
			pairing, err := s.identityPairing.Continue(workerContext, pairingID)
			if err == nil {
				if pairing.Status == identity.PairingCompleted || pairing.Status == identity.PairingFailed || pairing.Status == identity.PairingExpired || pairing.Status == identity.PairingCancelled {
					return
				}
				delay = time.Second
			} else {
				if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
					return
				}
				if s.logger != nil {
					s.logger.Warn("identity pairing step failed and will retry", "pairing_id", pairingID, "error_category", firstNonEmptyText(pairing.LastErrorCategory, "pairing_step_failed"))
				}
				if delay < 15*time.Second {
					delay *= 2
				}
			}
			select {
			case <-workerContext.Done():
				return
			case <-time.After(delay):
			}
		}
	}()
}

func (s *Server) stopIdentityPairingWorker(pairingID string) <-chan struct{} {
	if value, ok := s.identityPairingWorkers.Load(pairingID); ok {
		worker := value.(*identityPairingWorker)
		worker.cancel()
		return worker.done
	}
	done := make(chan struct{})
	close(done)
	return done
}

func (s *Server) startIdentityPairingCleanupWorker(pairingID string, processingDone <-chan struct{}) {
	if processingDone == nil {
		processingDone = s.stopIdentityPairingWorker(pairingID)
	}
	if _, loaded := s.identityCleanupWorkers.LoadOrStore(pairingID, struct{}{}); loaded {
		return
	}
	go func() {
		defer s.identityCleanupWorkers.Delete(pairingID)
		if processingDone != nil {
			select {
			case <-s.ctx.Done():
				return
			case <-processingDone:
			}
		}
		delay := time.Second
		for {
			pairing, err := s.identityPairing.Cleanup(s.ctx, pairingID)
			if err == nil {
				if pairing.CleanupStatus == identity.PairingCleanupCompleted {
					return
				}
				delay = time.Second
			} else {
				if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, identity.ErrPairingNotCancellable) {
					return
				}
				if s.logger != nil {
					s.logger.Warn("identity pairing cleanup failed and will retry", "pairing_id", pairingID, "error_category", firstNonEmptyText(pairing.LastErrorCategory, "pairing_cleanup_failed"))
				}
				if delay < 15*time.Second {
					delay *= 2
				}
			}
			select {
			case <-s.ctx.Done():
				return
			case <-time.After(delay):
			}
		}
	}()
}
