// Package cp 承载 CP/兄弟/姐妹关系的用例编排。
package cp

import (
	"context"
	"strings"
	"time"

	"hyapp/pkg/xerr"
	cpdomain "hyapp/services/user-service/internal/domain/cp"
)

// Repository 描述 CP 关系 service 需要的持久化能力，便于后续单元测试替换 MySQL 实现。
type Repository interface {
	ListApplications(ctx context.Context, userID int64, direction string, status string, page int32, pageSize int32, nowMs int64) ([]cpdomain.Application, int64, error)
	AcceptApplication(ctx context.Context, userID int64, applicationID string, nowMs int64) (cpdomain.Application, cpdomain.Relationship, error)
	RejectApplication(ctx context.Context, userID int64, applicationID string, reason string, nowMs int64) (cpdomain.Application, error)
	ListRelationships(ctx context.Context, userID int64, relationType string, page int32, pageSize int32) ([]cpdomain.Relationship, int64, error)
	PrepareBreakRelationship(ctx context.Context, userID int64, relationshipID string, commandID string, nowMs int64) (cpdomain.RelationshipBreakup, cpdomain.Relationship, error)
	ConfirmBreakRelationship(ctx context.Context, userID int64, relationshipID string, commandID string, walletTransactionID string, paidCoinAmount int64, coinBalanceAfter int64, nowMs int64) (cpdomain.RelationshipBreakup, cpdomain.Relationship, error)
	CancelBreakRelationship(ctx context.Context, userID int64, relationshipID string, commandID string, reason string, nowMs int64) (cpdomain.RelationshipBreakup, error)
	ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEvent, nowMs int64) (cpdomain.ConsumeResult, error)
}

// Service 只编排 CP 用例，不直接投递 IM；IM 由 user_outbox 下游消费者按事实事件处理。
type Service struct {
	repo  Repository
	clock func() time.Time
}

// New 创建 CP 关系 service。
func New(repo Repository) *Service {
	return &Service{
		repo:  repo,
		clock: func() time.Time { return time.Now().UTC() },
	}
}

// ListApplications 返回用户收到或发出的 CP 申请；过期固化在 repository 中完成。
func (s *Service) ListApplications(ctx context.Context, userID int64, direction string, status string, page int32, pageSize int32) ([]cpdomain.Application, int64, int64, error) {
	if err := requireUserID(userID); err != nil {
		return nil, 0, 0, err
	}
	nowMs := s.nowMs()
	applications, total, err := s.repo.ListApplications(ctx, userID, normalizeDirection(direction), normalizeStatus(status), page, pageSize, nowMs)
	return applications, total, nowMs, err
}

// AcceptApplication 同意申请并建立关系；同一用户只能有一个 active 关系的互斥由 repository 事务保证。
func (s *Service) AcceptApplication(ctx context.Context, userID int64, applicationID string) (cpdomain.Application, cpdomain.Relationship, error) {
	if err := requireUserID(userID); err != nil {
		return cpdomain.Application{}, cpdomain.Relationship{}, err
	}
	if strings.TrimSpace(applicationID) == "" {
		return cpdomain.Application{}, cpdomain.Relationship{}, xerr.New(xerr.InvalidArgument, "application_id is required")
	}
	return s.repo.AcceptApplication(ctx, userID, strings.TrimSpace(applicationID), s.nowMs())
}

// RejectApplication 拒绝申请；拒绝不阻断同一对用户其他类型的 pending 申请。
func (s *Service) RejectApplication(ctx context.Context, userID int64, applicationID string, reason string) (cpdomain.Application, error) {
	if err := requireUserID(userID); err != nil {
		return cpdomain.Application{}, err
	}
	if strings.TrimSpace(applicationID) == "" {
		return cpdomain.Application{}, xerr.New(xerr.InvalidArgument, "application_id is required")
	}
	return s.repo.RejectApplication(ctx, userID, strings.TrimSpace(applicationID), reason, s.nowMs())
}

// ListRelationships 返回用户当前 active 关系；当前产品固定每个用户同时只能有一条关系。
func (s *Service) ListRelationships(ctx context.Context, userID int64, relationType string, page int32, pageSize int32) ([]cpdomain.Relationship, int64, error) {
	if err := requireUserID(userID); err != nil {
		return nil, 0, err
	}
	return s.repo.ListRelationships(ctx, userID, normalizeRelationType(relationType), page, pageSize)
}

// PrepareBreakRelationship 创建解除关系占位；占位成功后 gateway 才能去 wallet-service 扣费。
func (s *Service) PrepareBreakRelationship(ctx context.Context, userID int64, relationshipID string, commandID string) (cpdomain.RelationshipBreakup, cpdomain.Relationship, error) {
	if err := requireBreakCommand(userID, relationshipID, commandID); err != nil {
		return cpdomain.RelationshipBreakup{}, cpdomain.Relationship{}, err
	}
	return s.repo.PrepareBreakRelationship(ctx, userID, strings.TrimSpace(relationshipID), strings.TrimSpace(commandID), s.nowMs())
}

// ConfirmBreakRelationship 在 wallet-service 扣费成功后确认解除；paidCoinAmount 必须等于 prepare 时冻结的配置费用。
func (s *Service) ConfirmBreakRelationship(ctx context.Context, userID int64, relationshipID string, commandID string, walletTransactionID string, paidCoinAmount int64, coinBalanceAfter int64) (cpdomain.RelationshipBreakup, cpdomain.Relationship, error) {
	if err := requireBreakCommand(userID, relationshipID, commandID); err != nil {
		return cpdomain.RelationshipBreakup{}, cpdomain.Relationship{}, err
	}
	if paidCoinAmount < 0 {
		return cpdomain.RelationshipBreakup{}, cpdomain.Relationship{}, xerr.New(xerr.InvalidArgument, "paid_coin_amount is invalid")
	}
	if paidCoinAmount > 0 && strings.TrimSpace(walletTransactionID) == "" {
		return cpdomain.RelationshipBreakup{}, cpdomain.Relationship{}, xerr.New(xerr.InvalidArgument, "wallet_transaction_id is required")
	}
	return s.repo.ConfirmBreakRelationship(ctx, userID, strings.TrimSpace(relationshipID), strings.TrimSpace(commandID), strings.TrimSpace(walletTransactionID), paidCoinAmount, coinBalanceAfter, s.nowMs())
}

// CancelBreakRelationship 只取消 pending 占位；已经 confirmed 的解除不能通过取消接口回滚。
func (s *Service) CancelBreakRelationship(ctx context.Context, userID int64, relationshipID string, commandID string, reason string) (cpdomain.RelationshipBreakup, error) {
	if err := requireBreakCommand(userID, relationshipID, commandID); err != nil {
		return cpdomain.RelationshipBreakup{}, err
	}
	return s.repo.CancelBreakRelationship(ctx, userID, strings.TrimSpace(relationshipID), strings.TrimSpace(commandID), reason, s.nowMs())
}

// ConsumeGiftEvent 消费 room-service 已扣费并落房间状态的送礼事实。
func (s *Service) ConsumeGiftEvent(ctx context.Context, event cpdomain.GiftEvent) (cpdomain.ConsumeResult, error) {
	event.CPRelationType = normalizeRelationType(event.CPRelationType)
	if event.CPRelationType == "" {
		return cpdomain.ConsumeResult{Consumed: false}, nil
	}
	// gift_value 是亲密值增量，沿用 room 热度结算后的值；0 值仍可创建申请，但不会增加已有关联亲密值。
	if event.GiftValue < 0 {
		event.GiftValue = 0
	}
	return s.repo.ConsumeGiftEvent(ctx, event, s.nowMs())
}

func (s *Service) nowMs() int64 {
	if s == nil || s.clock == nil {
		return time.Now().UTC().UnixMilli()
	}
	return s.clock().UTC().UnixMilli()
}

func requireUserID(userID int64) error {
	if userID <= 0 {
		return xerr.New(xerr.InvalidArgument, "user_id is required")
	}
	return nil
}

func requireBreakCommand(userID int64, relationshipID string, commandID string) error {
	if err := requireUserID(userID); err != nil {
		return err
	}
	if strings.TrimSpace(relationshipID) == "" {
		return xerr.New(xerr.InvalidArgument, "relationship_id is required")
	}
	if strings.TrimSpace(commandID) == "" {
		return xerr.New(xerr.InvalidArgument, "command_id is required")
	}
	if len(strings.TrimSpace(commandID)) > 128 {
		return xerr.New(xerr.InvalidArgument, "command_id is too long")
	}
	return nil
}

func normalizeDirection(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case "outgoing":
		return "outgoing"
	default:
		return "incoming"
	}
}

func normalizeStatus(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case cpdomain.ApplicationStatusPending,
		cpdomain.ApplicationStatusAccepted,
		cpdomain.ApplicationStatusRejected,
		cpdomain.ApplicationStatusExpired,
		cpdomain.ApplicationStatusBlocked:
		return strings.ToLower(strings.TrimSpace(value))
	default:
		return ""
	}
}

func normalizeRelationType(value string) string {
	switch strings.ToLower(strings.TrimSpace(value)) {
	case cpdomain.RelationTypeCP:
		return cpdomain.RelationTypeCP
	case cpdomain.RelationTypeBrother:
		return cpdomain.RelationTypeBrother
	case cpdomain.RelationTypeSister:
		return cpdomain.RelationTypeSister
	default:
		return ""
	}
}
