package adminuser

import (
	"context"
	"errors"
	"strconv"
	"strings"

	"hfb_sys/backend/internal/auditlog"
	"hfb_sys/backend/internal/model"

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

type Repository struct {
	db *gorm.DB
}

type AuditMeta = auditlog.Meta

func NewRepository(db *gorm.DB) *Repository {
	return &Repository{db: db}
}

func (r *Repository) List(ctx context.Context, page, pageSize int, query ListQuery) (*PaginatedResult, error) {
	var total int64
	countTx := applyUserFilter(r.db.WithContext(ctx).Table("users AS u"), query)
	if err := countTx.Count(&total).Error; err != nil {
		return nil, err
	}
	offset := (page - 1) * pageSize
	var rows []userRow
	listTx := r.db.WithContext(ctx).Table("users AS u").
		Select(`u.*,
			COALESCE(o.order_count, 0) AS order_count,
			COALESCE(l.listing_count, 0) AS listing_count,
			COALESCE(d.dispute_count, 0) AS dispute_count,
				COALESCE(df.deposit_free_used_cent, 0) AS deposit_free_used_cent`).
		Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS order_count FROM (SELECT renter_id AS user_id FROM rental_orders UNION ALL SELECT owner_id AS user_id FROM rental_orders) AS order_users GROUP BY user_id) AS o ON o.user_id = u.id").
		Joins("LEFT JOIN (SELECT owner_id AS user_id, COUNT(*) AS listing_count FROM rental_listings GROUP BY owner_id) AS l ON l.user_id = u.id").
		Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS dispute_count FROM (SELECT initiator_id AS user_id FROM disputes UNION ALL SELECT target_user_id AS user_id FROM disputes) AS dispute_users GROUP BY user_id) AS d ON d.user_id = u.id").
		Joins("LEFT JOIN (SELECT renter_id AS user_id, SUM(deposit_waived_amount_cent) AS deposit_free_used_cent FROM rental_orders WHERE status NOT IN ('completed', 'cancelled', 'closed') GROUP BY renter_id) AS df ON df.user_id = u.id")
	listTx = applyUserFilter(listTx, query)
	err := listTx.
		Order("u.id DESC").
		Offset(offset).Limit(pageSize).
		Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	items := make([]UserDTO, 0, len(rows))
	for _, row := range rows {
		items = append(items, row.toDTO())
	}
	return &PaginatedResult{Items: items, Total: total, Page: page, PageSize: pageSize}, nil
}

// applyUserFilter 将列表筛选条件应用到查询（基于 users 表别名 u）。
func applyUserFilter(tx *gorm.DB, query ListQuery) *gorm.DB {
	if query.Status != "" {
		tx = tx.Where("u.status = ?", query.Status)
	}
	if keyword := strings.TrimSpace(query.Keyword); keyword != "" {
		like := "%" + keyword + "%"
		// 关键词为纯数字时一并按用户 ID 精确匹配
		if id, err := strconv.ParseUint(keyword, 10, 64); err == nil {
			tx = tx.Where("u.id = ? OR u.phone LIKE ? OR u.nickname LIKE ?", id, like, like)
		} else {
			tx = tx.Where("u.phone LIKE ? OR u.nickname LIKE ?", like, like)
		}
	}
	return tx
}

func (r *Repository) Freeze(ctx context.Context, adminID uint64, userID uint64, req FreezeRequest, meta AuditMeta) (*UserDTO, error) {
	return r.updateStatus(ctx, adminID, userID, "frozen", "frozen", "admin_user.freeze", req.Reason, meta)
}

func (r *Repository) Unfreeze(ctx context.Context, adminID uint64, userID uint64, meta AuditMeta) (*UserDTO, error) {
	return r.updateStatus(ctx, adminID, userID, "active", "normal", "admin_user.unfreeze", "", meta)
}

func (r *Repository) SetDepositFreeQuota(ctx context.Context, adminID uint64, userID uint64, req DepositFreeQuotaRequest, meta AuditMeta) (*UserDTO, error) {
	amountCent := depositFreeQuotaAmountCent(req)
	if amountCent < 0 {
		return nil, ErrInvalidUser
	}
	err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var user model.User
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, userID).Error; err != nil {
			return err
		}
		beforeAmountCent := user.DepositFreeQuotaCent
		user.DepositFreeQuotaCent = amountCent
		if err := tx.Save(&user).Error; err != nil {
			return err
		}
		return appendAuditLog(tx, adminID, "admin_user.set_deposit_free_quota", user.ID, meta, map[string]any{
			"user_id":            user.ID,
			"before_amount_cent": beforeAmountCent,
			"after_amount_cent":  amountCent,
		})
	})
	if err != nil {
		return nil, err
	}
	return r.Find(ctx, userID)
}

func (r *Repository) updateStatus(ctx context.Context, adminID uint64, userID uint64, status string, riskStatus string, action string, reason string, meta AuditMeta) (*UserDTO, error) {
	err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var user model.User
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, userID).Error; err != nil {
			return err
		}
		beforeStatus := user.Status
		beforeRisk := user.RiskStatus
		user.Status = status
		user.RiskStatus = riskStatus
		if err := tx.Save(&user).Error; err != nil {
			return err
		}
		return appendAuditLog(tx, adminID, action, user.ID, meta, map[string]any{
			"user_id":            user.ID,
			"reason":             reason,
			"before_status":      beforeStatus,
			"after_status":       status,
			"before_risk_status": beforeRisk,
			"after_risk_status":  riskStatus,
		})
	})
	if err != nil {
		return nil, err
	}
	return r.Find(ctx, userID)
}

// RevokeRealname 撤销用户实名：将用户实名状态重置为未认证，并删除实名记录，
// 使用户可重新发起实名认证（用于实名姓名与收款账户不一致导致收款码登记失败的场景）。
func (r *Repository) RevokeRealname(ctx context.Context, adminID uint64, userID uint64, req RevokeRealnameRequest, meta AuditMeta) (*UserDTO, error) {
	err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var user model.User
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, userID).Error; err != nil {
			return err
		}
		var realnameCount int64
		if err := tx.Model(&model.UserRealname{}).Where("user_id = ?", userID).Count(&realnameCount).Error; err != nil {
			return err
		}
		// 已是未认证且无实名记录，无可撤销内容。
		if user.RealnameStatus == "unverified" && realnameCount == 0 {
			return ErrRealnameNotRevocable
		}
		beforeRealnameStatus := user.RealnameStatus
		user.RealnameStatus = "unverified"
		if err := tx.Save(&user).Error; err != nil {
			return err
		}
		if err := tx.Where("user_id = ?", userID).Delete(&model.UserRealname{}).Error; err != nil {
			return err
		}
		return appendAuditLog(tx, adminID, "admin_user.revoke_realname", user.ID, meta, map[string]any{
			"user_id":                user.ID,
			"reason":                 req.Reason,
			"before_realname_status": beforeRealnameStatus,
			"after_realname_status":  user.RealnameStatus,
		})
	})
	if err != nil {
		return nil, err
	}
	return r.Find(ctx, userID)
}

func (r *Repository) Find(ctx context.Context, userID uint64) (*UserDTO, error) {
	var row userRow
	err := r.db.WithContext(ctx).Table("users AS u").
		Select(`u.*,
			COALESCE(o.order_count, 0) AS order_count,
			COALESCE(l.listing_count, 0) AS listing_count,
			COALESCE(d.dispute_count, 0) AS dispute_count,
				COALESCE(df.deposit_free_used_cent, 0) AS deposit_free_used_cent`).
		Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS order_count FROM (SELECT renter_id AS user_id FROM rental_orders UNION ALL SELECT owner_id AS user_id FROM rental_orders) AS order_users GROUP BY user_id) AS o ON o.user_id = u.id").
		Joins("LEFT JOIN (SELECT owner_id AS user_id, COUNT(*) AS listing_count FROM rental_listings GROUP BY owner_id) AS l ON l.user_id = u.id").
		Joins("LEFT JOIN (SELECT user_id, COUNT(*) AS dispute_count FROM (SELECT initiator_id AS user_id FROM disputes UNION ALL SELECT target_user_id AS user_id FROM disputes) AS dispute_users GROUP BY user_id) AS d ON d.user_id = u.id").
		Joins("LEFT JOIN (SELECT renter_id AS user_id, SUM(deposit_waived_amount_cent) AS deposit_free_used_cent FROM rental_orders WHERE status NOT IN ('completed', 'cancelled', 'closed') GROUP BY renter_id) AS df ON df.user_id = u.id").
		Where("u.id = ?", userID).
		First(&row).Error
	if err != nil {
		return nil, err
	}
	dto := row.toDTO()
	return &dto, nil
}

type userRow struct {
	model.User
	OrderCount          int64
	ListingCount        int64
	DisputeCount        int64
	DepositFreeUsedCent int64
}

func (row userRow) toDTO() UserDTO {
	remaining := row.DepositFreeQuotaCent - row.DepositFreeUsedCent
	if remaining < 0 {
		remaining = 0
	}
	return UserDTO{
		ID:                       row.ID,
		Phone:                    row.Phone,
		Nickname:                 row.Nickname,
		RealnameStatus:           row.RealnameStatus,
		RiskStatus:               row.RiskStatus,
		CreditScore:              row.CreditScore,
		DepositFreeQuotaCent:     row.DepositFreeQuotaCent,
		DepositFreeUsedCent:      row.DepositFreeUsedCent,
		DepositFreeRemainingCent: remaining,
		Status:                   row.Status,
		OrderCount:               row.OrderCount,
		ListingCount:             row.ListingCount,
		DisputeCount:             row.DisputeCount,
		LastLoginAt:              row.LastLoginAt,
		CreatedAt:                row.CreatedAt,
		UpdatedAt:                row.UpdatedAt,
	}
}

func depositFreeQuotaAmountCent(req DepositFreeQuotaRequest) int64 {
	return req.AmountCent
}

func appendAuditLog(tx *gorm.DB, actorID uint64, action string, bizID uint64, meta AuditMeta, detail map[string]any) error {
	return auditlog.Append(tx, auditlog.Entry{
		ActorType: "admin",
		ActorID:   actorID,
		Action:    action,
		BizType:   "user",
		BizID:     &bizID,
		Meta:      meta,
		Detail:    detail,
	})
}

func IsNotFound(err error) bool {
	return errors.Is(err, gorm.ErrRecordNotFound)
}
