package adminrole

import (
	"context"
	"errors"
	"fmt"

	"hfb_sys/backend/internal/model"

	"github.com/redis/go-redis/v9"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type Repository struct {
	db    *gorm.DB
	redis *redis.Client
}

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

func (r *Repository) List(ctx context.Context) ([]RoleDTO, error) {
	db := r.db.WithContext(ctx)
	var roles []model.Role
	if err := db.Order("id ASC").Find(&roles).Error; err != nil {
		return nil, err
	}
	result := make([]RoleDTO, 0, len(roles))
	for _, role := range roles {
		var count int64
		if role.Code == "super_admin" {
			db.Model(&model.Permission{}).Count(&count)
		} else {
			db.Model(&model.RolePermission{}).Where("role_id = ?", role.ID).Count(&count)
		}
		result = append(result, RoleDTO{
			ID:          role.ID,
			Code:        role.Code,
			Name:        role.Name,
			Description: role.Description,
			PermCount:   int(count),
			CreatedAt:   role.CreatedAt,
			UpdatedAt:   role.UpdatedAt,
		})
	}
	return result, nil
}

func (r *Repository) FindByID(ctx context.Context, id uint64) (*RoleDTO, error) {
	var role model.Role
	if err := r.db.WithContext(ctx).First(&role, id).Error; err != nil {
		return nil, err
	}
	var perms []PermissionDTO
	var err error
	if role.Code == "super_admin" {
		perms, err = r.ListPermissions(ctx)
	} else {
		perms, err = r.getRolePermissions(ctx, id)
	}
	if err != nil {
		return nil, err
	}
	dto := RoleDTO{
		ID:          role.ID,
		Code:        role.Code,
		Name:        role.Name,
		Description: role.Description,
		PermCount:   len(perms),
		Permissions: perms,
		CreatedAt:   role.CreatedAt,
		UpdatedAt:   role.UpdatedAt,
	}
	return &dto, nil
}

func (r *Repository) Create(ctx context.Context, req CreateRoleRequest) (*RoleDTO, error) {
	role := model.Role{
		Code:        req.Code,
		Name:        req.Name,
		Description: req.Description,
	}
	if err := r.db.WithContext(ctx).Create(&role).Error; err != nil {
		return nil, err
	}
	return r.FindByID(ctx, role.ID)
}

func (r *Repository) Update(ctx context.Context, id uint64, req UpdateRoleRequest) (*RoleDTO, error) {
	db := r.db.WithContext(ctx)
	var role model.Role
	if err := db.First(&role, id).Error; err != nil {
		return nil, err
	}
	role.Name = req.Name
	role.Description = req.Description
	if err := db.Save(&role).Error; err != nil {
		return nil, err
	}
	return r.FindByID(ctx, id)
}

func (r *Repository) Delete(ctx context.Context, id uint64) error {
	db := r.db.WithContext(ctx)
	var role model.Role
	if err := db.First(&role, id).Error; err != nil {
		return err
	}
	if role.Code == "super_admin" {
		return ErrProtectedRole
	}
	adminIDs, err := r.adminIDsByRole(ctx, id)
	if err != nil {
		return err
	}
	err = db.Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("role_id = ?", id).Delete(&model.RolePermission{}).Error; err != nil {
			return err
		}
		if err := tx.Where("role_id = ?", id).Delete(&model.AdminUserRole{}).Error; err != nil {
			return err
		}
		if err := tx.Delete(&model.Role{}, id).Error; err != nil {
			return err
		}
		return r.bumpAdminTokenVersions(tx, adminIDs)
	})
	if err != nil {
		return err
	}
	r.invalidateAdminPermCaches(ctx, adminIDs)
	return nil
}

func (r *Repository) AssignPermissions(ctx context.Context, roleID uint64, permIDs []uint64) error {
	db := r.db.WithContext(ctx)
	var role model.Role
	if err := db.First(&role, roleID).Error; err != nil {
		return err
	}
	adminIDs, err := r.adminIDsByRole(ctx, roleID)
	if err != nil {
		return err
	}
	err = db.Transaction(func(tx *gorm.DB) error {
		if role.Code == "super_admin" {
			var allPermIDs []uint64
			if err := tx.Model(&model.Permission{}).Pluck("id", &allPermIDs).Error; err != nil {
				return err
			}
			permIDs = allPermIDs
		}
		if err := tx.Where("role_id = ?", roleID).Delete(&model.RolePermission{}).Error; err != nil {
			return err
		}
		for _, pid := range permIDs {
			rp := model.RolePermission{RoleID: roleID, PermissionID: pid}
			if err := tx.Clauses(clause.OnConflict{DoNothing: true}).Create(&rp).Error; err != nil {
				return err
			}
		}
		return r.bumpAdminTokenVersions(tx, adminIDs)
	})
	if err != nil {
		return err
	}
	r.invalidateAdminPermCaches(ctx, adminIDs)
	return nil
}

func (r *Repository) ListPermissions(ctx context.Context) ([]PermissionDTO, error) {
	var perms []model.Permission
	if err := r.db.WithContext(ctx).Order("resource ASC, action ASC").Find(&perms).Error; err != nil {
		return nil, err
	}
	result := make([]PermissionDTO, 0, len(perms))
	for _, p := range perms {
		result = append(result, PermissionDTO{
			ID: p.ID, Code: p.Code, Name: p.Name, Resource: p.Resource, Action: p.Action,
		})
	}
	return result, nil
}

func (r *Repository) getRolePermissions(ctx context.Context, roleID uint64) ([]PermissionDTO, error) {
	db := r.db.WithContext(ctx)
	var rps []model.RolePermission
	if err := db.Where("role_id = ?", roleID).Find(&rps).Error; err != nil {
		return nil, err
	}
	if len(rps) == 0 {
		return nil, nil
	}
	ids := make([]uint64, 0, len(rps))
	for _, rp := range rps {
		ids = append(ids, rp.PermissionID)
	}
	var perms []model.Permission
	if err := db.Where("id IN ?", ids).Find(&perms).Error; err != nil {
		return nil, err
	}
	result := make([]PermissionDTO, 0, len(perms))
	for _, p := range perms {
		result = append(result, PermissionDTO{
			ID: p.ID, Code: p.Code, Name: p.Name, Resource: p.Resource, Action: p.Action,
		})
	}
	return result, nil
}

func (r *Repository) adminIDsByRole(ctx context.Context, roleID uint64) ([]uint64, error) {
	var ids []uint64
	err := r.db.WithContext(ctx).Model(&model.AdminUserRole{}).
		Where("role_id = ?", roleID).
		Pluck("admin_user_id", &ids).Error
	return ids, err
}

func (r *Repository) bumpAdminTokenVersions(tx *gorm.DB, adminIDs []uint64) error {
	if len(adminIDs) == 0 {
		return nil
	}
	return tx.Model(&model.AdminUser{}).
		Where("id IN ?", adminIDs).
		UpdateColumn("token_version", gorm.Expr("token_version + 1")).Error
}

func (r *Repository) invalidateAdminPermCaches(ctx context.Context, adminIDs []uint64) {
	if r.redis == nil || len(adminIDs) == 0 {
		return
	}
	keys := make([]string, 0, len(adminIDs))
	for _, id := range adminIDs {
		keys = append(keys, fmt.Sprintf("admin:perms:%d", id))
	}
	_ = r.redis.Del(ctx, keys...).Err()
}

var ErrProtectedRole = errors.New("protected role")

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