// Package asset_audit 提供资产操作审计日志服务。
package asset_audit

import (
	"context"
	"fmt"
	"strings"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/internal/model/dto"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/logger"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

// AssetOperationLogStore 资产操作日志存储接口。
type AssetOperationLogStore interface {
	Create(ctx context.Context, log *model.AssetOperationLog) error
	ListByAssetPaged(
		ctx context.Context,
		assetType string,
		assetID uint,
		page int,
		pageSize int,
		operationType string,
		resultStatus string,
	) ([]*model.AssetOperationLog, int64, error)
}

// OperationLogger 资产审计记录接口。
type OperationLogger interface {
	LogOperation(ctx context.Context, log *model.AssetOperationLog)
}

// Service 资产审计服务。
type Service struct {
	store AssetOperationLogStore
	db    *gorm.DB
}

// ListByAssetParams 按资产查询日志参数。
type ListByAssetParams struct {
	AssetType     string
	AssetID       uint
	Page          int
	PageSize      int
	OperationType string
	ResultStatus  string
}

// NewService 创建资产审计服务实例。
func NewService(store AssetOperationLogStore, db *gorm.DB) *Service {
	return &Service{
		store: store,
		db:    db,
	}
}

// LogOperation 记录资产操作日志（异步写入，不阻塞主流程）。
func (s *Service) LogOperation(ctx context.Context, log *model.AssetOperationLog) {
	if s == nil || s.store == nil || log == nil {
		return
	}

	go func() {
		if err := s.store.Create(context.Background(), log); err != nil {
			logger.GetAppLogger().Error("写入资产操作日志失败",
				zap.String("asset_type", log.AssetType),
				zap.Uint("asset_id", log.AssetID),
				zap.String("operation_type", log.OperationType),
				zap.String("result_status", log.ResultStatus),
				zap.Error(err))
		}
	}()
}

// ListByAsset 按资产分页查询操作日志。
func (s *Service) ListByAsset(ctx context.Context, params ListByAssetParams) (*dto.AssetOperationLogListResponse, error) {
	if s == nil || s.store == nil {
		return &dto.AssetOperationLogListResponse{
			Total:    0,
			Page:     1,
			PageSize: constants.DefaultPageSize,
			Items:    []*dto.AssetOperationLogItem{},
		}, nil
	}

	page := params.Page
	pageSize := params.PageSize
	if page <= 0 {
		page = 1
	}
	if pageSize <= 0 {
		pageSize = constants.DefaultPageSize
	}
	if pageSize > 100 {
		pageSize = 100
	}

	logs, total, err := s.store.ListByAssetPaged(
		ctx,
		NormalizeAssetType(params.AssetType),
		params.AssetID,
		page,
		pageSize,
		params.OperationType,
		params.ResultStatus,
	)
	if err != nil {
		return nil, err
	}

	operatorViews := s.loadOperatorViews(ctx, logs)
	items := make([]*dto.AssetOperationLogItem, 0, len(logs))
	for _, item := range logs {
		items = append(items, toAssetOperationLogItem(item, operatorViews[item.ID]))
	}

	return &dto.AssetOperationLogListResponse{
		Total:    total,
		Page:     page,
		PageSize: pageSize,
		Items:    items,
	}, nil
}

func toAssetOperationLogItem(log *model.AssetOperationLog, operatorView operatorView) *dto.AssetOperationLogItem {
	if log == nil {
		return nil
	}

	operationBefore, operationAfter := normalizeOperationContent(
		log.OperationType,
		map[string]any(log.BeforeData),
		map[string]any(log.AfterData),
	)

	return &dto.AssetOperationLogItem{
		ID:                     log.ID,
		CreatedAt:              log.CreatedAt,
		OperatorID:             log.OperatorID,
		OperatorType:           operatorView.TypeName,
		OperatorTypeCode:       log.OperatorType,
		OperatorName:           operatorView.Name,
		AssetType:              log.AssetType,
		AssetID:                log.AssetID,
		AssetIdentifier:        log.AssetIdentifier,
		OperationType:          log.OperationType,
		OperationDesc:          log.OperationDesc,
		OperationContentBefore: operationBefore,
		OperationContentAfter:  operationAfter,
		OperationFieldsDesc:    getOperationFieldDesc(log.OperationType),
		BeforeData:             map[string]any(log.BeforeData),
		AfterData:              map[string]any(log.AfterData),
		RequestID:              log.RequestID,
		IPAddress:              log.IPAddress,
		UserAgent:              log.UserAgent,
		RequestPath:            log.RequestPath,
		RequestMethod:          log.RequestMethod,
		ResultStatus:           log.ResultStatus,
		ErrorCode:              log.ErrorCode,
		ErrorMsg:               log.ErrorMsg,
		BatchTotal:             log.BatchTotal,
		SuccessCount:           log.SuccessCount,
		FailCount:              log.FailCount,
	}
}

type operatorView struct {
	TypeName string
	Name     string
}

func (s *Service) loadOperatorViews(ctx context.Context, logs []*model.AssetOperationLog) map[uint]operatorView {
	result := make(map[uint]operatorView, len(logs))
	if len(logs) == 0 {
		return result
	}

	accountIDs := make([]uint, 0)
	customerIDs := make([]uint, 0)
	accountIDSet := make(map[uint]struct{})
	customerIDSet := make(map[uint]struct{})

	for _, log := range logs {
		if log == nil {
			continue
		}

		typeName := operatorTypeDisplayName(log.OperatorType)
		result[log.ID] = operatorView{
			TypeName: typeName,
			Name:     fallbackResolvedOperatorName(log.OperatorName, typeName, log.OperatorID),
		}

		if log.OperatorID == nil {
			continue
		}

		switch log.OperatorType {
		case constants.AssetAuditOperatorTypeAdmin, constants.AssetAuditOperatorTypeAgent, constants.AssetAuditOperatorTypeEnterprise:
			if _, exists := accountIDSet[*log.OperatorID]; !exists {
				accountIDSet[*log.OperatorID] = struct{}{}
				accountIDs = append(accountIDs, *log.OperatorID)
			}
		case constants.AssetAuditOperatorTypePersonal:
			if _, exists := customerIDSet[*log.OperatorID]; !exists {
				customerIDSet[*log.OperatorID] = struct{}{}
				customerIDs = append(customerIDs, *log.OperatorID)
			}
		}
	}

	if s.db == nil {
		return result
	}

	accountMap, shopMap, enterpriseMap := s.loadAccountRelatedMaps(ctx, accountIDs)
	customerMap := s.loadPersonalCustomerMap(ctx, customerIDs)

	for _, log := range logs {
		if log == nil {
			continue
		}

		view := result[log.ID]
		if log.OperatorID == nil {
			result[log.ID] = finalizeOperatorView(view, log.OperatorName, log.OperatorType, log.OperatorID)
			continue
		}

		switch log.OperatorType {
		case constants.AssetAuditOperatorTypeAdmin, constants.AssetAuditOperatorTypeAgent, constants.AssetAuditOperatorTypeEnterprise:
			if account, ok := accountMap[*log.OperatorID]; ok {
				view.Name = buildAccountOperatorName(account, shopMap, enterpriseMap)
			}
		case constants.AssetAuditOperatorTypePersonal:
			if customer, ok := customerMap[*log.OperatorID]; ok {
				if nickname := strings.TrimSpace(customer.Nickname); nickname != "" {
					view.Name = nickname
				}
			}
		}

		result[log.ID] = finalizeOperatorView(view, log.OperatorName, log.OperatorType, log.OperatorID)
	}

	return result
}

func (s *Service) loadAccountRelatedMaps(ctx context.Context, accountIDs []uint) (map[uint]*model.Account, map[uint]string, map[uint]string) {
	accountMap := make(map[uint]*model.Account)
	shopMap := make(map[uint]string)
	enterpriseMap := make(map[uint]string)
	if len(accountIDs) == 0 || s.db == nil {
		return accountMap, shopMap, enterpriseMap
	}

	var accounts []*model.Account
	if err := s.db.WithContext(ctx).Unscoped().Where("id IN ?", accountIDs).Find(&accounts).Error; err != nil {
		return accountMap, shopMap, enterpriseMap
	}

	shopIDs := make([]uint, 0)
	shopIDSet := make(map[uint]struct{})
	enterpriseIDs := make([]uint, 0)
	enterpriseIDSet := make(map[uint]struct{})

	for _, account := range accounts {
		if account == nil {
			continue
		}
		accountMap[account.ID] = account
		if account.ShopID != nil {
			if _, exists := shopIDSet[*account.ShopID]; !exists {
				shopIDSet[*account.ShopID] = struct{}{}
				shopIDs = append(shopIDs, *account.ShopID)
			}
		}
		if account.EnterpriseID != nil {
			if _, exists := enterpriseIDSet[*account.EnterpriseID]; !exists {
				enterpriseIDSet[*account.EnterpriseID] = struct{}{}
				enterpriseIDs = append(enterpriseIDs, *account.EnterpriseID)
			}
		}
	}

	if len(shopIDs) > 0 {
		var shops []*model.Shop
		if err := s.db.WithContext(ctx).Unscoped().Where("id IN ?", shopIDs).Find(&shops).Error; err == nil {
			for _, shop := range shops {
				if shop == nil {
					continue
				}
				shopMap[shop.ID] = strings.TrimSpace(shop.ShopName)
			}
		}
	}

	if len(enterpriseIDs) > 0 {
		var enterprises []*model.Enterprise
		if err := s.db.WithContext(ctx).Unscoped().Where("id IN ?", enterpriseIDs).Find(&enterprises).Error; err == nil {
			for _, enterprise := range enterprises {
				if enterprise == nil {
					continue
				}
				enterpriseMap[enterprise.ID] = strings.TrimSpace(enterprise.EnterpriseName)
			}
		}
	}

	return accountMap, shopMap, enterpriseMap
}

func (s *Service) loadPersonalCustomerMap(ctx context.Context, customerIDs []uint) map[uint]*model.PersonalCustomer {
	result := make(map[uint]*model.PersonalCustomer)
	if len(customerIDs) == 0 || s.db == nil {
		return result
	}

	var customers []*model.PersonalCustomer
	if err := s.db.WithContext(ctx).Unscoped().Where("id IN ?", customerIDs).Find(&customers).Error; err != nil {
		return result
	}

	for _, customer := range customers {
		if customer == nil {
			continue
		}
		result[customer.ID] = customer
	}

	return result
}

func finalizeOperatorView(view operatorView, storedName string, typeCode string, operatorID *uint) operatorView {
	if strings.TrimSpace(view.TypeName) == "" {
		view.TypeName = operatorTypeDisplayName(typeCode)
	}
	view.Name = resolveOperatorName(view.Name, storedName, view.TypeName, operatorID)
	return view
}

func operatorTypeDisplayName(typeCode string) string {
	switch strings.TrimSpace(typeCode) {
	case constants.AssetAuditOperatorTypeSystem:
		return "系统"
	case constants.AssetAuditOperatorTypeAdmin:
		return "平台用户"
	case constants.AssetAuditOperatorTypeAgent:
		return "代理账号"
	case constants.AssetAuditOperatorTypeEnterprise:
		return "企业账号"
	case constants.AssetAuditOperatorTypePersonal:
		return "个人客户"
	default:
		if strings.TrimSpace(typeCode) == "" {
			return "未知"
		}
		return strings.TrimSpace(typeCode)
	}
}

func fallbackResolvedOperatorName(storedName string, typeName string, operatorID *uint) string {
	storedName = strings.TrimSpace(storedName)
	if storedName == "" || isPlaceholderOperatorName(storedName) {
		return typeNameWithOptionalID(typeName, operatorID)
	}
	return storedName
}

func resolveOperatorName(resolvedName string, storedName string, typeName string, operatorID *uint) string {
	if strings.TrimSpace(resolvedName) != "" && !isPlaceholderOperatorName(resolvedName) {
		return strings.TrimSpace(resolvedName)
	}
	return fallbackResolvedOperatorName(storedName, typeName, operatorID)
}

func isPlaceholderOperatorName(name string) bool {
	name = strings.TrimSpace(name)
	if name == "" {
		return true
	}
	for _, prefix := range []string{"后台用户#", "代理用户#", "企业用户#", "个人客户#", "用户#"} {
		if strings.HasPrefix(name, prefix) {
			return true
		}
	}
	return false
}

func typeNameWithOptionalID(typeName string, operatorID *uint) string {
	if operatorID == nil || *operatorID == 0 {
		return typeName
	}
	return fmt.Sprintf("%s(ID:%d)", typeName, *operatorID)
}

func buildAccountOperatorName(account *model.Account, shopMap map[uint]string, enterpriseMap map[uint]string) string {
	if account == nil {
		return ""
	}

	username := strings.TrimSpace(account.Username)
	switch account.UserType {
	case constants.UserTypeAgent:
		return composeReadableName(lookupNameByPtr(account.ShopID, shopMap), username)
	case constants.UserTypeEnterprise:
		return composeReadableName(lookupNameByPtr(account.EnterpriseID, enterpriseMap), username)
	default:
		return username
	}
}

func lookupNameByPtr(id *uint, nameMap map[uint]string) string {
	if id == nil {
		return ""
	}
	return strings.TrimSpace(nameMap[*id])
}

func composeReadableName(primary string, secondary string) string {
	primary = strings.TrimSpace(primary)
	secondary = strings.TrimSpace(secondary)
	switch {
	case primary != "" && secondary != "" && primary != secondary:
		return fmt.Sprintf("%s(%s)", primary, secondary)
	case primary != "":
		return primary
	default:
		return secondary
	}
}
