package wecom

import (
	"context"
	"strings"

	"github.com/bytedance/sonic"
	"gorm.io/gorm"

	"github.com/break/junhong_cmp_fiber/internal/model"
	approvalquery "github.com/break/junhong_cmp_fiber/internal/query/approval"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
)

// ApprovalApproverProjection 是企微审批节点成员到系统账号的可选映射。
type ApprovalApproverProjection struct {
	WeComUserID string `json:"wecom_userid"`
	AccountID   *uint  `json:"account_id,omitempty"`
	AccountName string `json:"account_name"`
}

// ApprovalChannelProjection 是通用审批 Query 返回的企微本地只读扩展。
type ApprovalChannelProjection struct {
	SPNo      string                       `json:"sp_no"`
	SPStatus  int                          `json:"sp_status"`
	Approvers []ApprovalApproverProjection `json:"approvers"`
}

// ApprovalProjectionResolver 从已同步详情快照批量投影审批人，不调用企业微信接口。
type ApprovalProjectionResolver struct {
	db *gorm.DB
}

// NewApprovalProjectionResolver 创建企业微信审批读取投影 Resolver。
func NewApprovalProjectionResolver(db *gorm.DB) *ApprovalProjectionResolver {
	return &ApprovalProjectionResolver{db: db}
}

// Resolve 按当前页审批实例批量读取快照，并批量映射同企业下的系统账号。
func (r *ApprovalProjectionResolver) Resolve(
	ctx context.Context,
	_ approvalquery.Viewer,
	references []approvalquery.ExtensionReference,
) (map[uint]any, error) {
	result := make(map[uint]any)
	if len(references) == 0 {
		return result, nil
	}
	if r == nil || r.db == nil {
		return nil, errors.New(errors.CodeInternalError, "企业微信审批读取投影未配置")
	}
	instanceIDs := weComExtensionInstanceIDs(references)
	if len(instanceIDs) == 0 {
		return result, nil
	}
	var contexts []model.WeComApprovalContext
	if err := r.db.WithContext(ctx).Where("approval_instance_id IN ?", instanceIDs).Find(&contexts).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询企业微信审批详情快照失败")
	}
	applicationIDs := make([]uint, 0, len(contexts))
	for _, channelContext := range contexts {
		applicationIDs = append(applicationIDs, channelContext.ApplicationID)
	}
	corpByApplication, err := r.loadApplicationCorps(ctx, applicationIDs)
	if err != nil {
		return nil, err
	}
	userIDsByCorp := make(map[string][]string)
	approversByInstance := make(map[uint][]string, len(contexts))
	for _, channelContext := range contexts {
		userIDs := approvalNodeUserIDs(channelContext.LatestDetailSnapshot)
		approversByInstance[channelContext.ApprovalInstanceID] = userIDs
		corpID := corpByApplication[channelContext.ApplicationID]
		userIDsByCorp[corpID] = append(userIDsByCorp[corpID], userIDs...)
	}
	accounts, err := r.loadBoundAccounts(ctx, userIDsByCorp)
	if err != nil {
		return nil, err
	}
	for _, channelContext := range contexts {
		corpID := corpByApplication[channelContext.ApplicationID]
		approvers := make([]ApprovalApproverProjection, 0, len(approversByInstance[channelContext.ApprovalInstanceID]))
		for _, userID := range approversByInstance[channelContext.ApprovalInstanceID] {
			item := ApprovalApproverProjection{WeComUserID: userID}
			if account, exists := accounts[weComAccountKey(corpID, userID)]; exists {
				accountID := account.ID
				item.AccountID = &accountID
				item.AccountName = account.Username
			}
			approvers = append(approvers, item)
		}
		result[channelContext.ApprovalInstanceID] = ApprovalChannelProjection{
			SPNo: channelContext.SPNo, SPStatus: channelContext.LatestSPStatus, Approvers: approvers,
		}
	}
	return result, nil
}

func weComExtensionInstanceIDs(references []approvalquery.ExtensionReference) []uint {
	seen := make(map[uint]struct{}, len(references))
	ids := make([]uint, 0, len(references))
	for _, reference := range references {
		if reference.InstanceID == 0 || reference.Provider != constants.IntegrationProviderWeCom {
			continue
		}
		if _, exists := seen[reference.InstanceID]; exists {
			continue
		}
		seen[reference.InstanceID] = struct{}{}
		ids = append(ids, reference.InstanceID)
	}
	return ids
}

func (r *ApprovalProjectionResolver) loadApplicationCorps(ctx context.Context, applicationIDs []uint) (map[uint]string, error) {
	result := make(map[uint]string)
	if len(applicationIDs) == 0 {
		return result, nil
	}
	var applications []model.WeComApplication
	if err := r.db.WithContext(ctx).Select("id", "corp_id").Where("id IN ?", applicationIDs).Find(&applications).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量查询企业微信应用企业标识失败")
	}
	for _, application := range applications {
		result[application.ID] = application.CorpID
	}
	return result, nil
}

func (r *ApprovalProjectionResolver) loadBoundAccounts(ctx context.Context, userIDsByCorp map[string][]string) (map[string]model.Account, error) {
	result := make(map[string]model.Account)
	query := r.db.WithContext(ctx).Model(&model.Account{}).Where("1 = 0")
	hasCondition := false
	for corpID, userIDs := range userIDsByCorp {
		corpID = strings.TrimSpace(corpID)
		userIDs = uniqueStrings(userIDs)
		if corpID == "" || len(userIDs) == 0 {
			continue
		}
		query = query.Or("wecom_corp_id = ? AND wecom_userid IN ? AND status = ?", corpID, userIDs, constants.StatusEnabled)
		hasCondition = true
	}
	if !hasCondition {
		return result, nil
	}
	var accounts []model.Account
	if err := query.Select("id", "username", "wecom_corp_id", "wecom_userid").Find(&accounts).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "批量映射企业微信审批人账号失败")
	}
	for _, account := range accounts {
		result[weComAccountKey(account.WeComCorpID, account.WeComUserID)] = account
	}
	return result, nil
}

func approvalNodeUserIDs(snapshot []byte) []string {
	var detail struct {
		SPRecords []struct {
			Details []struct {
				Approver struct {
					UserID string `json:"userid"`
				} `json:"approver"`
			} `json:"details"`
		} `json:"sp_record"`
	}
	if sonic.Unmarshal(snapshot, &detail) != nil {
		return []string{}
	}
	values := make([]string, 0)
	for _, record := range detail.SPRecords {
		for _, node := range record.Details {
			values = append(values, node.Approver.UserID)
		}
	}
	return uniqueStrings(values)
}

func uniqueStrings(values []string) []string {
	seen := make(map[string]struct{}, len(values))
	result := make([]string, 0, len(values))
	for _, value := range values {
		value = strings.TrimSpace(value)
		if value == "" {
			continue
		}
		if _, exists := seen[value]; exists {
			continue
		}
		seen[value] = struct{}{}
		result = append(result, value)
	}
	return result
}

func weComAccountKey(corpID, userID string) string {
	return strings.TrimSpace(corpID) + "\x00" + strings.TrimSpace(userID)
}

var _ approvalquery.ChannelExtensionResolver = (*ApprovalProjectionResolver)(nil)
