package mysql

import (
	"context"
	"errors"
	"strings"

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

	"wucher/internal/domain/auth"
)

type RoleRepository struct {
	db *gorm.DB
}

func NewRoleRepository(db *gorm.DB) *RoleRepository {
	return &RoleRepository{db: db}
}

func (r *RoleRepository) CreateRole(ctx context.Context, role *auth.Role) error {
	if actor := actorUserIDFromContext(ctx); len(actor) > 0 {
		role.CreatedBy = actor
		role.UpdatedBy = actor
	}
	return r.db.WithContext(ctx).Create(role).Error
}

func (r *RoleRepository) UpdateRole(ctx context.Context, role *auth.Role) error {
	if actor := actorUserIDFromContext(ctx); len(actor) > 0 {
		role.UpdatedBy = actor
	}
	return r.db.WithContext(ctx).Save(role).Error
}

func (r *RoleRepository) DeleteRole(ctx context.Context, id []byte) error {
	return r.db.WithContext(ctx).Delete(&auth.Role{}, "id = ?", id).Error
}

func (r *RoleRepository) GetRoleByID(ctx context.Context, id []byte) (*auth.Role, error) {
	var role auth.Role
	err := r.db.WithContext(ctx).First(&role, "id = ?", id).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &role, err
}

func (r *RoleRepository) GetRoleByName(ctx context.Context, name string) (*auth.Role, error) {
	var role auth.Role
	err := r.db.WithContext(ctx).Where("name = ?", name).First(&role).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &role, err
}

func (r *RoleRepository) HasRolePermission(ctx context.Context, roleID []byte, permissionKey string) (bool, error) {
	var count int64
	err := r.db.WithContext(ctx).
		Table("role_permissions rp").
		Joins("JOIN permissions p ON p.id = rp.permission_id").
		Where("rp.role_id = ? AND p.`key` = ?", roleID, permissionKey).
		Count(&count).Error
	if err != nil {
		return false, err
	}
	return count > 0, nil
}

func (r *RoleRepository) GetPermissionByID(ctx context.Context, id []byte) (*auth.Permission, error) {
	var perm auth.Permission
	err := r.db.WithContext(ctx).First(&perm, "id = ?", id).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &perm, err
}

func (r *RoleRepository) GetPermissionByKey(ctx context.Context, key string) (*auth.Permission, error) {
	var perm auth.Permission
	err := r.db.WithContext(ctx).Where("`key` = ?", strings.ToLower(strings.TrimSpace(key))).First(&perm).Error
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, nil
	}
	return &perm, err
}

func (r *RoleRepository) UpdatePermission(ctx context.Context, permission *auth.Permission) error {
	if actor := actorUserIDFromContext(ctx); len(actor) > 0 {
		permission.UpdatedBy = actor
	}
	return r.db.WithContext(ctx).Save(permission).Error
}

func (r *RoleRepository) ListPermissionsByRoleID(ctx context.Context, roleID []byte) ([]auth.Permission, error) {
	var perms []auth.Permission
	err := r.db.WithContext(ctx).
		Table("permissions p").
		Select("p.*").
		Joins("JOIN role_permissions rp ON rp.permission_id = p.id").
		Where("rp.role_id = ?", roleID).
		Order("p.name ASC").
		Find(&perms).Error
	if err != nil {
		return nil, err
	}
	return perms, nil
}

func (r *RoleRepository) AssignPermission(ctx context.Context, roleID, permissionID []byte) (*auth.RolePermission, error) {
	rp := &auth.RolePermission{RoleID: roleID, PermissionID: permissionID}
	if actor := actorUserIDFromContext(ctx); len(actor) > 0 {
		rp.CreatedBy = actor
		rp.UpdatedBy = actor
	}
	err := r.db.WithContext(ctx).
		Clauses(clause.OnConflict{
			Columns:   []clause.Column{{Name: "role_id"}, {Name: "permission_id"}},
			DoNothing: true,
		}).
		Create(rp).Error
	if err != nil {
		return nil, err
	}

	// Already exists, return current row.
	if len(rp.ID) == 0 {
		var existing auth.RolePermission
		if err := r.db.WithContext(ctx).
			Where("role_id = ? AND permission_id = ?", roleID, permissionID).
			First(&existing).Error; err != nil {
			return nil, err
		}
		return &existing, nil
	}
	return rp, nil
}

func (r *RoleRepository) RemovePermission(ctx context.Context, roleID, permissionID []byte) error {
	return r.db.WithContext(ctx).
		Delete(&auth.RolePermission{}, "role_id = ? AND permission_id = ?", roleID, permissionID).
		Error
}

func (r *RoleRepository) ListPermissions(ctx context.Context, filter string, sort string, limit, offset int) ([]auth.Permission, int64, error) {
	var perms []auth.Permission
	var total int64

	query := r.db.WithContext(ctx).Model(&auth.Permission{})
	if filter != "" {
		like := "%" + strings.ToLower(filter) + "%"
		query = query.Where("LOWER(name) LIKE ? OR LOWER(`key`) LIKE ?", like, like)
	}
	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}
	if sort != "" {
		query = query.Order(sort)
	} else {
		query = query.Order("created_at DESC")
	}
	if limit > 0 {
		query = query.Limit(limit).Offset(offset)
	}
	if err := query.Find(&perms).Error; err != nil {
		return nil, 0, err
	}
	return perms, total, nil
}

func (r *RoleRepository) ListRoles(ctx context.Context, filterName, sort string, limit, offset int) ([]auth.Role, int64, error) {
	var roles []auth.Role
	var total int64

	query := r.db.WithContext(ctx).Model(&auth.Role{})

	if filterName != "" {
		query = query.Where("LOWER(name) LIKE ?", "%"+strings.ToLower(filterName)+"%")
	}

	if err := query.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	if sort != "" {
		query = query.Order(sort)
	} else {
		query = query.Order("created_at DESC")
	}

	if limit > 0 {
		query = query.Limit(limit).Offset(offset)
	}

	if err := query.Find(&roles).Error; err != nil {
		return nil, 0, err
	}

	return roles, total, nil
}
