package commerce

import (
	"context"
	"fmt"
	"sort"
	"strings"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type agentEvaluationMetrics struct {
	PeriodStart     time.Time
	PeriodEnd       time.Time
	GrossSales      int
	RefundAmount    int
	NetSales        int
	RefundRateBPS   int
	ActiveCustomers int
	SubsidyLoss     int
}

func (s *Service) EvaluateAgents(ctx context.Context, limit int) (map[string]any, error) {
	if limit <= 0 {
		limit = 200
	}
	var agents []UserAgent
	if err := s.db.WithContext(ctx).Where("status IN ?", []string{"active", "observing"}).Order("last_evaluated_at IS NOT NULL ASC, last_evaluated_at ASC, id ASC").Limit(limit).Find(&agents).Error; err != nil {
		return nil, err
	}
	summary := map[string]any{"checked": 0, "observe": 0, "restore_active": 0, "downgrade": 0, "return_member": 0, "risk_locked": 0, "failed": 0}
	for _, candidate := range agents {
		action, err := s.evaluateOneAgent(ctx, candidate.ID)
		if err != nil {
			summary["failed"] = summary["failed"].(int) + 1
			if s.logger != nil {
				s.logger.Warn("agent lifecycle evaluation failed", "agent_id", candidate.ID, "error", err)
			}
			continue
		}
		summary["checked"] = summary["checked"].(int) + 1
		if _, ok := summary[action]; ok {
			summary[action] = summary[action].(int) + 1
		}
	}
	return summary, nil
}

func (s *Service) evaluateOneAgent(ctx context.Context, agentID int64) (string, error) {
	action := "none"
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txService := s.withDB(tx)
		var agent UserAgent
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", agentID).First(&agent).Error; err != nil {
			return err
		}
		if agent.Status != "active" && agent.Status != "observing" {
			action = "skipped"
			return nil
		}
		if agent.AgentTierID == nil {
			action = "skipped"
			return nil
		}
		var tier AgentTier
		if err := tx.Where("id = ? AND enabled = ?", *agent.AgentTierID, true).First(&tier).Error; err != nil {
			return err
		}
		now := time.Now().UTC()
		metrics, err := txService.calculateAgentEvaluationMetrics(ctx, agent.UserID, now)
		if err != nil {
			return err
		}
		passed := metrics.NetSales >= tier.MonthlySalesRequired && metrics.ActiveCustomers >= tier.MonthlyActiveCustomersRequired
		riskReasons := []string{}
		if tier.MaxRefundRateBps > 0 && metrics.RefundAmount > 0 && metrics.RefundRateBPS >= tier.MaxRefundRateBps {
			riskReasons = append(riskReasons, fmt.Sprintf("refund rate %dbps >= %dbps", metrics.RefundRateBPS, tier.MaxRefundRateBps))
		}
		if tier.MaxMonthlySubsidyCents > 0 && metrics.SubsidyLoss >= tier.MaxMonthlySubsidyCents && agent.AgentBalance < tier.MinBalanceForSubsidyCents {
			riskReasons = append(riskReasons, fmt.Sprintf("subsidy loss %d with balance %d", metrics.SubsidyLoss, agent.AgentBalance))
		}
		beforeStatus := agent.Status
		beforeTierID := agent.AgentTierID
		reason := ""
		if len(riskReasons) > 0 {
			reason = strings.Join(riskReasons, "; ")
			action = "risk_locked"
			agent.Status = "risk_locked"
			agent.RiskLockedAt = &now
			agent.RiskReason = &reason
			passed = false
			if err := tx.Table("agent_risk_events").Create(map[string]any{"agent_id": agent.ID, "user_id": agent.UserID, "event_type": "auto_risk_lock", "severity": "critical", "status": "open", "reason": reason, "snapshot": agentMetricsSnapshot(metrics, tier), "created_at": now, "updated_at": now}).Error; err != nil {
				return err
			}
		} else if passed && agent.Status == "observing" {
			action = "restore_active"
			reason = "rolling 30-day metrics recovered"
			agent.Status = "active"
			agent.ObserveStartedAt = nil
			agent.ObserveUntil = nil
		} else if !passed {
			reasons := []string{}
			if metrics.NetSales < tier.MonthlySalesRequired {
				reasons = append(reasons, fmt.Sprintf("net sales %d < %d", metrics.NetSales, tier.MonthlySalesRequired))
			}
			if metrics.ActiveCustomers < tier.MonthlyActiveCustomersRequired {
				reasons = append(reasons, fmt.Sprintf("active customers %d < %d", metrics.ActiveCustomers, tier.MonthlyActiveCustomersRequired))
			}
			reason = strings.Join(reasons, "; ")
			if !tier.AutoDowngradeEnabled {
				action = "not_pass_manual_only"
			} else if agent.Status == "active" {
				days := tier.ObserveDays
				if days <= 0 {
					days = 7
				}
				until := now.Add(time.Duration(days) * 24 * time.Hour)
				action = "observe"
				agent.Status = "observing"
				agent.ObserveStartedAt = &now
				agent.ObserveUntil = &until
			} else if agent.Status == "observing" && agent.ObserveUntil != nil && !agent.ObserveUntil.After(now) {
				target, err := txService.agentDowngradeTarget(ctx, tier, metrics)
				if err != nil {
					return err
				}
				agent.ObserveStartedAt = nil
				agent.ObserveUntil = nil
				if target != nil {
					action = "downgrade"
					agent.Status = "active"
					agent.AgentTierID = &target.ID
					agent.RiskReason = nil
					if err := txService.syncMembershipAgentTier(ctx, agent.UserID, &target.Code); err != nil {
						return err
					}
				} else {
					action = "return_member"
					agent.Status = "suspended"
					agent.AgentTierID = nil
					if err := txService.syncMembershipAgentTier(ctx, agent.UserID, nil); err != nil {
						return err
					}
				}
			}
		}
		snapshot := agentMetricsSnapshot(metrics, tier)
		agent.LastEvaluatedAt = &now
		agent.LastEvaluationSnapshot = jsonRawFromAny(snapshot)
		if err := tx.Save(&agent).Error; err != nil {
			return err
		}
		if action != "none" && action != "skipped" {
			if err := tx.Table("agent_status_logs").Create(map[string]any{"agent_id": agent.ID, "user_id": agent.UserID, "source": "system", "action": action, "before_status": beforeStatus, "after_status": agent.Status, "before_tier_id": beforeTierID, "after_tier_id": agent.AgentTierID, "reason": reason, "snapshot": snapshot, "created_at": now, "updated_at": now}).Error; err != nil {
				return err
			}
		}
		if err := tx.Table("agent_evaluation_logs").Create(map[string]any{"agent_id": agent.ID, "user_id": agent.UserID, "tier_id": tier.ID, "period_start": metrics.PeriodStart, "period_end": metrics.PeriodEnd, "net_sales": metrics.NetSales, "gross_sales": metrics.GrossSales, "refund_amount": metrics.RefundAmount, "refund_rate_bps": metrics.RefundRateBPS, "active_customers": metrics.ActiveCustomers, "subsidy_loss": metrics.SubsidyLoss, "agent_balance": agent.AgentBalance, "passed": boolInt(passed), "risk_locked": boolInt(len(riskReasons) > 0), "action": action, "reason": nullStringAny(reason), "snapshot": snapshot, "created_at": now, "updated_at": now}).Error; err != nil {
			return err
		}
		return nil
	})
	if err == nil {
		s.notifyAgentLifecycleTransition(ctx, agentID, action)
	}
	return action, err
}

func (s *Service) calculateAgentEvaluationMetrics(ctx context.Context, agentUserID int64, now time.Time) (agentEvaluationMetrics, error) {
	metrics := agentEvaluationMetrics{PeriodStart: now.Add(-30 * 24 * time.Hour), PeriodEnd: now}
	var customerIDs []int64
	if err := s.db.WithContext(ctx).Table("agent_customers").Where("agent_user_id = ? AND status = ?", agentUserID, "active").Pluck("customer_user_id", &customerIDs).Error; err != nil {
		return metrics, err
	}
	participants := append([]int64{agentUserID}, customerIDs...)
	var orders []Order
	if err := s.db.WithContext(ctx).Where("user_id IN ? AND created_at >= ? AND status IN ?", participants, metrics.PeriodStart, []string{"paid", "refunded"}).Find(&orders).Error; err != nil {
		return metrics, err
	}
	netByCustomer := map[int64]int{}
	for _, order := range orders {
		paid := maxInt(0, order.TotalAmount-order.DiscountAmount)
		var refunded int64
		if err := s.db.WithContext(ctx).Model(&OrderRefund{}).Where("order_id = ? AND status = ?", order.ID, "success").Select("COALESCE(SUM(refund_amount),0)").Scan(&refunded).Error; err != nil {
			return metrics, err
		}
		refundedAmount := minInt(paid, int(refunded))
		metrics.GrossSales += paid
		metrics.RefundAmount += refundedAmount
		netByCustomer[order.UserID] += maxInt(0, paid-refundedAmount)
	}
	metrics.NetSales = maxInt(0, metrics.GrossSales-metrics.RefundAmount)
	if metrics.GrossSales > 0 {
		metrics.RefundRateBPS = metrics.RefundAmount * 10000 / metrics.GrossSales
	}
	for _, amount := range netByCustomer {
		if amount > 0 {
			metrics.ActiveCustomers++
		}
	}
	var subsidy int64
	if err := s.db.WithContext(ctx).Model(&AgentSettlement{}).Where("user_id = ? AND settlement_type = ? AND status IN ? AND created_at >= ? AND delta_amount < 0", agentUserID, "agent_subsidy", []string{"pending", "frozen", "settled"}, metrics.PeriodStart).Select("COALESCE(SUM(ABS(delta_amount)),0)").Scan(&subsidy).Error; err != nil {
		return metrics, err
	}
	metrics.SubsidyLoss = int(subsidy)
	return metrics, nil
}

func (s *Service) agentDowngradeTarget(ctx context.Context, current AgentTier, metrics agentEvaluationMetrics) (*AgentTier, error) {
	var tiers []AgentTier
	if err := s.db.WithContext(ctx).Where("enabled = ? AND id <> ?", true, current.ID).Find(&tiers).Error; err != nil {
		return nil, err
	}
	candidates := []AgentTier{}
	for _, tier := range tiers {
		if compareAgentTierRank(tier, current) < 0 && metrics.NetSales >= tier.MonthlySalesRequired && metrics.ActiveCustomers >= tier.MonthlyActiveCustomersRequired {
			candidates = append(candidates, tier)
		}
	}
	if len(candidates) == 0 {
		return nil, nil
	}
	sort.Slice(candidates, func(i, j int) bool {
		return compareAgentTierRank(candidates[i], candidates[j]) > 0
	})
	return &candidates[0], nil
}

func agentTierRank(tier AgentTier) [6]int {
	return [6]int{
		tier.MonthlySalesRequired,
		tier.MonthlyActiveCustomersRequired,
		tier.MinTotalSpend,
		tier.MinMemberLevel,
		-tier.CostRate,
		tier.SortOrder,
	}
}

func compareAgentTierRank(left AgentTier, right AgentTier) int {
	leftRank := agentTierRank(left)
	rightRank := agentTierRank(right)
	for i := range leftRank {
		if leftRank[i] < rightRank[i] {
			return -1
		}
		if leftRank[i] > rightRank[i] {
			return 1
		}
	}
	return 0
}

func (s *Service) notifyAgentLifecycleTransition(ctx context.Context, agentID int64, action string) {
	if action == "none" || action == "skipped" || action == "not_pass_manual_only" {
		return
	}
	var agent UserAgent
	if err := s.db.WithContext(ctx).Where("id = ?", agentID).First(&agent).Error; err != nil {
		return
	}
	title := "代理状态已更新"
	content := "您的代理状态已由系统自动更新，请进入代理中心查看。"
	switch action {
	case "risk_locked":
		title = "代理权益已风控冻结"
		content = "系统检测到代理账户存在风险，相关权益已冻结，请联系客服处理。"
	case "restore_active":
		title = "代理观察期已解除"
		content = "您的代理考核指标已恢复达标，代理状态已恢复正常。"
	case "observe":
		title = "代理进入降级观察期"
		content = "您的代理考核暂未达标，已进入降级观察期。"
	case "downgrade":
		title = "代理档位已自动降级"
		content = "观察期结束后仍未达标，代理档位已自动调整。"
	case "return_member":
		title = "代理身份已退回会员"
		content = "观察期结束后仍未满足代理档位要求，已退回普通会员推广身份。"
	}
	link := "/agent"
	notification := Notification{UserID: agent.UserID, Category: "agent", Level: "warning", SenderType: "system", Title: title, Content: content, Link: &link, ActionURL: &link}
	if err := s.db.WithContext(ctx).Create(&notification).Error; err != nil && s.logger != nil {
		s.logger.Warn("agent lifecycle notification failed", "agent_id", agentID, "action", action, "error", err)
	}
}

func (s *Service) syncMembershipAgentTier(ctx context.Context, userID int64, tierCode *string) error {
	membership, err := s.getOrCreateMembership(ctx, userID)
	if err != nil {
		return err
	}
	return s.db.WithContext(ctx).Model(&UserMembership{}).Where("id = ?", membership.ID).Update("agent_tier", tierCode).Error
}

func agentMetricsSnapshot(metrics agentEvaluationMetrics, tier AgentTier) map[string]any {
	return map[string]any{"period_start": metrics.PeriodStart, "period_end": metrics.PeriodEnd, "gross_sales": metrics.GrossSales, "refund_amount": metrics.RefundAmount, "net_sales": metrics.NetSales, "refund_rate_bps": metrics.RefundRateBPS, "active_customers": metrics.ActiveCustomers, "subsidy_loss": metrics.SubsidyLoss, "tier": map[string]any{"id": tier.ID, "code": tier.Code, "monthly_sales_required": tier.MonthlySalesRequired, "monthly_active_customers_required": tier.MonthlyActiveCustomersRequired, "max_refund_rate_bps": tier.MaxRefundRateBps, "max_monthly_subsidy_cents": tier.MaxMonthlySubsidyCents, "min_balance_for_subsidy_cents": tier.MinBalanceForSubsidyCents}}
}
