package services

import (
	"context"
	"database/sql"
	"fmt"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"veza-backend-api/internal/database"
	"veza-backend-api/internal/models"
)

// RBACService handles role-based access control
type RBACService struct {
	db     *database.Database
	logger *zap.Logger
}

// NewRBACService creates a new RBAC service
func NewRBACService(db *database.Database, logger *zap.Logger) *RBACService {
	return &RBACService{
		db:     db,
		logger: logger,
	}
}

// Role represents a user role
type Role struct {
	ID          uuid.UUID    `json:"id"`
	Name        string       `json:"name"`
	Description string       `json:"description"`
	Permissions []Permission `json:"permissions"`
	IsSystem    bool         `json:"is_system"`
	CreatedAt   string       `json:"created_at"`
	UpdatedAt   string       `json:"updated_at"`
}

// Permission represents a permission
type Permission struct {
	ID          uuid.UUID `json:"id"`
	Name        string    `json:"name"`
	Description string    `json:"description"`
	Resource    string    `json:"resource"`
	Action      string    `json:"action"`
	CreatedAt   string    `json:"created_at"`
}

// UserRole represents a user's role assignment
type UserRole struct {
	ID     uuid.UUID `json:"id"`
	UserID uuid.UUID `json:"user_id"`
	RoleID uuid.UUID `json:"role_id"`
	Role   *Role     `json:"role,omitempty"`
}

// CreateRole creates a new role
func (s *RBACService) CreateRole(ctx context.Context, name, description string, permissions []uuid.UUID) (*Role, error) {
	// Check if role already exists
	var count int
	err := s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM roles WHERE name = $1", name).Scan(&count)
	if err != nil {
		return nil, fmt.Errorf("failed to check role existence: %w", err)
	}
	if count > 0 {
		return nil, fmt.Errorf("role with name '%s' already exists", name)
	}

	// Create role
	var roleID uuid.UUID
	query := `
		INSERT INTO roles (id, name, description, is_system, created_at, updated_at)
		VALUES (gen_random_uuid(), $1, $2, false, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
		RETURNING id
	`

	err = s.db.QueryRowContext(ctx, query, name, description).Scan(&roleID)
	if err != nil {
		return nil, fmt.Errorf("failed to create role: %w", err)
	}

	// Assign permissions to role
	if len(permissions) > 0 {
		for _, permID := range permissions {
			_, err = s.db.ExecContext(ctx, `
				INSERT INTO role_permissions (role_id, permission_id, created_at)
				VALUES ($1, $2, CURRENT_TIMESTAMP)
			`, roleID, permID)
			if err != nil {
				s.logger.Error("Failed to assign permission to role", zap.Error(err))
				// Continue with other permissions
			}
		}
	}

	// Get the created role with permissions
	role, err := s.GetRoleByID(ctx, roleID)
	if err != nil {
		return nil, fmt.Errorf("failed to get created role: %w", err)
	}

	s.logger.Info("Role created successfully", zap.String("role_name", name), zap.String("role_id", roleID.String()))
	return role, nil
}

// GetRoleByID gets a role by ID
func (s *RBACService) GetRoleByID(ctx context.Context, roleID uuid.UUID) (*Role, error) {
	query := `
		SELECT r.id, r.name, r.description, r.is_system, r.created_at, r.updated_at
		FROM roles r
		WHERE r.id = $1
	`

	var role Role
	err := s.db.QueryRowContext(ctx, query, roleID).Scan(
		&role.ID, &role.Name, &role.Description, &role.IsSystem, &role.CreatedAt, &role.UpdatedAt,
	)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("role not found")
		}
		return nil, fmt.Errorf("failed to get role: %w", err)
	}

	// Get permissions for this role
	permissions, err := s.GetRolePermissions(ctx, roleID)
	if err != nil {
		s.logger.Error("Failed to get role permissions", zap.Error(err))
	} else {
		role.Permissions = permissions
	}

	return &role, nil
}

// GetRolePermissions gets permissions for a role
func (s *RBACService) GetRolePermissions(ctx context.Context, roleID uuid.UUID) ([]Permission, error) {
	query := `
		SELECT p.id, p.name, p.description, p.resource, p.action, p.created_at
		FROM permissions p
		JOIN role_permissions rp ON p.id = rp.permission_id
		WHERE rp.role_id = $1
		ORDER BY p.name
	`

	rows, err := s.db.QueryContext(ctx, query, roleID)
	if err != nil {
		return nil, fmt.Errorf("failed to get role permissions: %w", err)
	}
	defer rows.Close()

	var permissions []Permission
	for rows.Next() {
		var perm Permission
		err := rows.Scan(&perm.ID, &perm.Name, &perm.Description, &perm.Resource, &perm.Action, &perm.CreatedAt)
		if err != nil {
			s.logger.Error("Failed to scan permission", zap.Error(err))
			continue
		}
		permissions = append(permissions, perm)
	}

	return permissions, nil
}

// AssignRoleToUser assigns a role to a user
// MIGRATION UUID: userID migré vers uuid.UUID, roleID aussi
// Transactionnelle : Toutes les vérifications et l'INSERT sont dans une seule transaction avec FOR UPDATE
func (s *RBACService) AssignRoleToUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID) error {
	return s.db.GormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		var err error
		// 1. VALIDATION : User existe ? (SELECT avec FOR UPDATE pour éviter race condition)
		var user models.User
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&user, userID).Error; err != nil {
			if err == gorm.ErrRecordNotFound {
				return fmt.Errorf("user not found")
			}
			return fmt.Errorf("AssignRoleToUser: failed to check user existence: %w", err)
		}

		// 2. VALIDATION : Role existe ? (SELECT avec FOR UPDATE pour éviter race condition)
		var role models.Role
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&role, roleID).Error; err != nil {
			if err == gorm.ErrRecordNotFound {
				return fmt.Errorf("role not found")
			}
			return fmt.Errorf("AssignRoleToUser: failed to check role existence: %w", err)
		}

		// 1. Vérifier si l'utilisateur a déjà ce rôle (avec verrou)
		var existingRole models.UserRole
		err = tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("user_id = ? AND role_id = ?", userID, roleID).
			First(&existingRole).Error

		if err == nil {
			return fmt.Errorf("role already assigned to user")
		}
		if err != gorm.ErrRecordNotFound {
			return fmt.Errorf("failed to check existing role: %w", err)
		}

		// 4. INSERTION : Assignation (INSERT dans la transaction)
		// Note: 'role' column is required by schema (legacy/redundant field)
		err = tx.Exec(`
			INSERT INTO user_roles (id, user_id, role_id, role, created_at)
			VALUES (gen_random_uuid(), ?, ?, ?, CURRENT_TIMESTAMP)
		`, userID, roleID, role.Name).Error
		if err != nil {
			// Si contrainte UNIQUE violée (race condition détectée), la contrainte DB gère cela
			// La vérification du doublon avant l'INSERT devrait gérer la plupart des cas
			return fmt.Errorf("AssignRoleToUser: failed to assign role to user: %w", err)
		}

		// 5. LOG (dans la transaction, mais ne dépend pas d'états non commit)
		s.logger.Info("Role assigned to user successfully",
			zap.String("user_id", userID.String()),
			zap.String("role_id", roleID.String()),
		)

		// 6. RETOUR nil = commit automatique
		return nil
	})
}

// RemoveRoleFromUser removes a role from a user
// MIGRATION UUID: userID migré vers uuid.UUID, roleID aussi
func (s *RBACService) RemoveRoleFromUser(ctx context.Context, userID uuid.UUID, roleID uuid.UUID) error {
	result, err := s.db.ExecContext(ctx, `
		DELETE FROM user_roles 
		WHERE user_id = $1 AND role_id = $2
	`, userID, roleID)
	if err != nil {
		return fmt.Errorf("failed to remove role from user: %w", err)
	}

	rowsAffected, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rowsAffected == 0 {
		return fmt.Errorf("role not assigned to user")
	}

	s.logger.Info("Role removed from user successfully", zap.String("user_id", userID.String()), zap.String("role_id", roleID.String()))
	return nil
}

// GetUserRoles gets all roles for a user
func (s *RBACService) GetUserRoles(ctx context.Context, userID uuid.UUID) ([]*Role, error) {
	query := `
		SELECT r.id, r.name, r.description, r.is_system, r.created_at, r.updated_at
		FROM roles r
		JOIN user_roles ur ON r.id = ur.role_id
		WHERE ur.user_id = $1
		ORDER BY r.name
	`

	rows, err := s.db.QueryContext(ctx, query, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to get user roles: %w", err)
	}
	defer rows.Close()

	var roles []*Role
	for rows.Next() {
		var role Role
		err := rows.Scan(&role.ID, &role.Name, &role.Description, &role.IsSystem, &role.CreatedAt, &role.UpdatedAt)
		if err != nil {
			s.logger.Error("Failed to scan role", zap.Error(err))
			continue
		}

		// Get permissions for this role
		permissions, err := s.GetRolePermissions(ctx, role.ID)
		if err != nil {
			s.logger.Error("Failed to get role permissions", zap.Error(err))
		} else {
			role.Permissions = permissions
		}

		roles = append(roles, &role)
	}

	return roles, nil
}

// CheckPermission checks if a user has a specific permission
func (s *RBACService) CheckPermission(ctx context.Context, userID uuid.UUID, resource, action string) (bool, error) {
	query := `
		SELECT COUNT(*) 
		FROM permissions p
		JOIN role_permissions rp ON p.id = rp.permission_id
		JOIN user_roles ur ON rp.role_id = ur.role_id
		WHERE ur.user_id = $1 AND p.resource = $2 AND p.action = $3
	`

	var count int
	err := s.db.QueryRowContext(ctx, query, userID, resource, action).Scan(&count)
	if err != nil {
		return false, fmt.Errorf("failed to check permission: %w", err)
	}

	return count > 0, nil
}

// GetUserPermissions gets all permissions for a user
func (s *RBACService) GetUserPermissions(ctx context.Context, userID uuid.UUID) ([]Permission, error) {
	query := `
		SELECT DISTINCT p.id, p.name, p.description, p.resource, p.action, p.created_at
		FROM permissions p
		JOIN role_permissions rp ON p.id = rp.permission_id
		JOIN user_roles ur ON rp.role_id = ur.role_id
		WHERE ur.user_id = $1
		ORDER BY p.resource, p.action
	`

	rows, err := s.db.QueryContext(ctx, query, userID)
	if err != nil {
		return nil, fmt.Errorf("failed to get user permissions: %w", err)
	}
	defer rows.Close()

	var permissions []Permission
	for rows.Next() {
		var perm Permission
		err := rows.Scan(&perm.ID, &perm.Name, &perm.Description, &perm.Resource, &perm.Action, &perm.CreatedAt)
		if err != nil {
			s.logger.Error("Failed to scan permission", zap.Error(err))
			continue
		}
		permissions = append(permissions, perm)
	}

	return permissions, nil
}

// CreatePermission creates a new permission
func (s *RBACService) CreatePermission(ctx context.Context, name, description, resource, action string) (*Permission, error) {
	// Check if permission already exists
	var count int
	err := s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM permissions WHERE resource = $1 AND action = $2", resource, action).Scan(&count)
	if err != nil {
		return nil, fmt.Errorf("failed to check permission existence: %w", err)
	}
	if count > 0 {
		return nil, fmt.Errorf("permission with resource '%s' and action '%s' already exists", resource, action)
	}

	// Create permission
	var permID uuid.UUID
	query := `
		INSERT INTO permissions (id, name, description, resource, action, created_at)
		VALUES (gen_random_uuid(), $1, $2, $3, $4, CURRENT_TIMESTAMP)
		RETURNING id
	`

	err = s.db.QueryRowContext(ctx, query, name, description, resource, action).Scan(&permID)
	if err != nil {
		return nil, fmt.Errorf("failed to create permission: %w", err)
	}

	permission := &Permission{
		ID:          permID,
		Name:        name,
		Description: description,
		Resource:    resource,
		Action:      action,
	}

	s.logger.Info("Permission created successfully", zap.String("permission_name", name))
	return permission, nil
}

// GetAllRoles gets all roles
func (s *RBACService) GetAllRoles(ctx context.Context) ([]*Role, error) {
	query := `
		SELECT id, name, description, is_system, created_at, updated_at
		FROM roles
		ORDER BY name
	`

	rows, err := s.db.QueryContext(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("failed to get roles: %w", err)
	}
	defer rows.Close()

	var roles []*Role
	for rows.Next() {
		var role Role
		err := rows.Scan(&role.ID, &role.Name, &role.Description, &role.IsSystem, &role.CreatedAt, &role.UpdatedAt)
		if err != nil {
			s.logger.Error("Failed to scan role", zap.Error(err))
			continue
		}

		// Get permissions for this role
		permissions, err := s.GetRolePermissions(ctx, role.ID)
		if err != nil {
			s.logger.Error("Failed to get role permissions", zap.Error(err))
		} else {
			role.Permissions = permissions
		}

		roles = append(roles, &role)
	}

	return roles, nil
}
