package adminuser

import (
	"errors"

	"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(page, pageSize int) (*PaginatedResult, error) {
	var total int64
	if err := r.db.Model(&model.User{}).Count(&total).Error; err != nil {
		return nil, err
	}
	offset := (page - 1) * pageSize
	var rows []userRow
	err := r.db.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`).
		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").
		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
}

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

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

func (r *Repository) updateStatus(adminID uint64, userID uint64, status string, riskStatus string, action string, reason string, meta AuditMeta) (*UserDTO, error) {
	err := r.db.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(userID)
}

func (r *Repository) Find(userID uint64) (*UserDTO, error) {
	var row userRow
	err := r.db.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`).
		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").
		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
}

func (row userRow) toDTO() UserDTO {
	return UserDTO{
		ID:             row.ID,
		Phone:          row.Phone,
		Nickname:       row.Nickname,
		RealnameStatus: row.RealnameStatus,
		RiskStatus:     row.RiskStatus,
		CreditScore:    row.CreditScore,
		Status:         row.Status,
		OrderCount:     row.OrderCount,
		ListingCount:   row.ListingCount,
		DisputeCount:   row.DisputeCount,
		LastLoginAt:    row.LastLoginAt,
		CreatedAt:      row.CreatedAt,
		UpdatedAt:      row.UpdatedAt,
	}
}

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)
}
