// Package approval 提供渠道无关审批实例的读取模型和权限投影。
package approval

import (
	"context"
	"strings"
	"time"

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

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/middleware"
)

// Viewer 是业务权限 Adapter 判定当前读取主体所需的最小身份。
type Viewer struct {
	AccountID uint
	UserType  int
}

// BusinessReference 是通用审批实例指向的稳定业务引用。
type BusinessReference struct {
	InstanceID   uint
	BusinessType string
	BusinessID   uint
}

// BusinessProjection 是业务权限 Adapter 返回的安全摘要和处理状态。
type BusinessProjection struct {
	Allowed              bool
	Summary              string
	ProcessingStatus     int
	ProcessingStatusName string
	ProcessingSummary    string
}

// BusinessProjectionResolver 批量复核业务资源当前权限并投影处理摘要。
type BusinessProjectionResolver interface {
	Resolve(ctx context.Context, viewer Viewer, references []BusinessReference) (map[uint]BusinessProjection, error)
}

// ExtensionReference 是渠道 Adapter 读取本地扩展快照所需的最小引用。
type ExtensionReference struct {
	InstanceID  uint
	Provider    string
	ExternalRef string
}

// ChannelExtensionResolver 可为平台主体批量补充渠道专属本地快照。
// 实现不得在 Query 请求中实时调用外部审批平台。
type ChannelExtensionResolver interface {
	Resolve(ctx context.Context, viewer Viewer, references []ExtensionReference) (map[uint]any, error)
}

// Projection 是通用审批 Query 对业务列表和详情输出的稳定读取模型。
type Projection struct {
	ID                   uint      `json:"id"`
	BusinessType         string    `json:"business_type"`
	BusinessID           uint      `json:"business_id"`
	BusinessSummary      string    `json:"business_summary"`
	SubmitterAccountID   uint      `json:"submitter_account_id"`
	SubmitterName        string    `json:"submitter_name"`
	Provider             string    `json:"provider"`
	Status               int       `json:"status"`
	StatusName           string    `json:"status_name"`
	StatusChangedAt      time.Time `json:"status_changed_at"`
	ProcessingStatus     int       `json:"processing_status"`
	ProcessingStatusName string    `json:"processing_status_name"`
	ProcessingSummary    string    `json:"processing_summary"`
	ChannelExtension     any       `json:"channel_extension,omitempty"`
}

// Query 批量读取通用审批事实，并通过业务 Adapter 复核当前权限。
type Query struct {
	db                *gorm.DB
	businessResolver  BusinessProjectionResolver
	extensionResolver ChannelExtensionResolver
}

// NewQuery 创建通用审批 Query。
func NewQuery(db *gorm.DB, businessResolver BusinessProjectionResolver, extensionResolver ChannelExtensionResolver) *Query {
	return &Query{db: db, businessResolver: businessResolver, extensionResolver: extensionResolver}
}

// GetByID 读取单条通用审批投影；资源不存在、引用失效或无权限统一返回禁止访问。
func (q *Query) GetByID(ctx context.Context, instanceID uint) (*Projection, error) {
	items, err := q.BatchByIDs(ctx, []uint{instanceID})
	if err != nil {
		return nil, err
	}
	if len(items) != 1 {
		return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
	}
	return &items[0], nil
}

// BatchByIDs 按调用方当前页实例 ID 批量返回有权读取的审批投影，并保持输入顺序。
func (q *Query) BatchByIDs(ctx context.Context, instanceIDs []uint) ([]Projection, error) {
	ids, err := normalizeInstanceIDs(instanceIDs)
	if err != nil {
		return nil, err
	}
	if len(ids) == 0 {
		return []Projection{}, nil
	}
	if q == nil || q.db == nil || q.businessResolver == nil {
		return nil, errors.New(errors.CodeInternalError, "通用审批 Query 未完整配置")
	}

	var records []model.ApprovalInstance
	if err := q.db.WithContext(ctx).Where("id IN ?", ids).Find(&records).Error; err != nil {
		return nil, errors.Wrap(errors.CodeDatabaseError, err, "查询通用审批实例失败")
	}
	recordByID := make(map[uint]model.ApprovalInstance, len(records))
	references := make([]BusinessReference, 0, len(records))
	for _, record := range records {
		recordByID[record.ID] = record
		references = append(references, BusinessReference{
			InstanceID: record.ID, BusinessType: record.BusinessType, BusinessID: record.BusinessID,
		})
	}

	viewer := Viewer{AccountID: middleware.GetUserIDFromContext(ctx), UserType: middleware.GetUserTypeFromContext(ctx)}
	if viewer.AccountID == 0 || viewer.UserType == 0 {
		return nil, errors.New(errors.CodeForbidden)
	}
	businessByID, err := q.businessResolver.Resolve(ctx, viewer, references)
	if err != nil {
		return nil, err
	}
	extensionByID, err := q.resolveExtensions(ctx, viewer, records, businessByID)
	if err != nil {
		return nil, err
	}

	items := make([]Projection, 0, len(records))
	for _, id := range ids {
		record, exists := recordByID[id]
		business, allowed := businessByID[id]
		if !exists || !allowed || !business.Allowed {
			continue
		}
		items = append(items, project(record, business, extensionByID[id]))
	}
	return items, nil
}

func (q *Query) resolveExtensions(
	ctx context.Context,
	viewer Viewer,
	records []model.ApprovalInstance,
	businessByID map[uint]BusinessProjection,
) (map[uint]any, error) {
	if q.extensionResolver == nil || viewer.UserType == constants.UserTypeAgent || viewer.UserType == constants.UserTypeEnterprise {
		return map[uint]any{}, nil
	}
	if viewer.UserType != constants.UserTypeSuperAdmin && viewer.UserType != constants.UserTypePlatform {
		return map[uint]any{}, nil
	}
	references := make([]ExtensionReference, 0, len(records))
	for _, record := range records {
		business, exists := businessByID[record.ID]
		if !exists || !business.Allowed {
			continue
		}
		references = append(references, ExtensionReference{
			InstanceID: record.ID, Provider: record.Provider, ExternalRef: record.ExternalRef,
		})
	}
	if len(references) == 0 {
		return map[uint]any{}, nil
	}
	return q.extensionResolver.Resolve(ctx, viewer, references)
}

func normalizeInstanceIDs(instanceIDs []uint) ([]uint, error) {
	if len(instanceIDs) > constants.ApprovalQueryMaxBatchSize {
		return nil, errors.New(errors.CodeInvalidParam, "单次最多查询 100 条审批摘要")
	}
	seen := make(map[uint]struct{}, len(instanceIDs))
	ids := make([]uint, 0, len(instanceIDs))
	for _, id := range instanceIDs {
		if id == 0 {
			return nil, errors.New(errors.CodeInvalidParam)
		}
		if _, exists := seen[id]; exists {
			continue
		}
		seen[id] = struct{}{}
		ids = append(ids, id)
	}
	return ids, nil
}

func project(record model.ApprovalInstance, business BusinessProjection, extension any) Projection {
	return Projection{
		ID: record.ID, BusinessType: record.BusinessType, BusinessID: record.BusinessID,
		BusinessSummary:    business.Summary,
		SubmitterAccountID: record.SubmitterAccountID, SubmitterName: submitterName(record.SubmitterSnapshot),
		Provider: record.Provider, Status: record.Status, StatusName: constants.GetApprovalStatusName(record.Status),
		StatusChangedAt:  record.StatusChangedAt,
		ProcessingStatus: business.ProcessingStatus, ProcessingStatusName: business.ProcessingStatusName,
		ProcessingSummary: business.ProcessingSummary, ChannelExtension: extension,
	}
}

func submitterName(snapshot []byte) string {
	var value struct {
		AccountName string `json:"account_name"`
	}
	if sonic.Unmarshal(snapshot, &value) != nil || strings.TrimSpace(value.AccountName) == "" {
		return constants.ApprovalUnknownSubmitterName
	}
	return strings.TrimSpace(value.AccountName)
}
