package services

import (
	"context"
	"database/sql"
	"fmt"

	"veza-backend-api/internal/database"

	"go.uber.org/zap"
)

// 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          int64        `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          int64  `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     int64 `json:"id"`
	UserID int64 `json:"user_id"`
	RoleID int64 `json:"role_id"`
	Role   *Role `json:"role,omitempty"`
}

// CreateRole creates a new role
func (s *RBACService) CreateRole(ctx context.Context, name, description string, permissions []int64) (*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 int64
	query := `
		INSERT INTO roles (name, description, is_system, created_at, updated_at)
		VALUES ($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.Int64("role_id", roleID))
	return role, nil
}

// GetRoleByID gets a role by ID
func (s *RBACService) GetRoleByID(ctx context.Context, roleID int64) (*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 int64) ([]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
func (s *RBACService) AssignRoleToUser(ctx context.Context, userID, roleID int64) error {
	// Check if user exists
	var userCount int
	err := s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM users WHERE id = $1", userID).Scan(&userCount)
	if err != nil {
		return fmt.Errorf("failed to check user existence: %w", err)
	}
	if userCount == 0 {
		return fmt.Errorf("user not found")
	}

	// Check if role exists
	var roleCount int
	err = s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM roles WHERE id = $1", roleID).Scan(&roleCount)
	if err != nil {
		return fmt.Errorf("failed to check role existence: %w", err)
	}
	if roleCount == 0 {
		return fmt.Errorf("role not found")
	}

	// Check if role is already assigned
	var assignmentCount int
	err = s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM user_roles WHERE user_id = $1 AND role_id = $2", userID, roleID).Scan(&assignmentCount)
	if err != nil {
		return fmt.Errorf("failed to check role assignment: %w", err)
	}
	if assignmentCount > 0 {
		return fmt.Errorf("role already assigned to user")
	}

	// Assign role to user
	_, err = s.db.ExecContext(ctx, `
		INSERT INTO user_roles (user_id, role_id, created_at)
		VALUES ($1, $2, CURRENT_TIMESTAMP)
	`, userID, roleID)
	if err != nil {
		return fmt.Errorf("failed to assign role to user: %w", err)
	}

	s.logger.Info("Role assigned to user successfully", zap.Int64("user_id", userID), zap.Int64("role_id", roleID))
	return nil
}

// RemoveRoleFromUser removes a role from a user
func (s *RBACService) RemoveRoleFromUser(ctx context.Context, userID, roleID int64) 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.Int64("user_id", userID), zap.Int64("role_id", roleID))
	return nil
}

// GetUserRoles gets all roles for a user
func (s *RBACService) GetUserRoles(ctx context.Context, userID int64) ([]*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 int64, 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 int64) ([]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 int64
	query := `
		INSERT INTO permissions (name, description, resource, action, created_at)
		VALUES ($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
}
