// Package grpc provides gRPC client implementations for service clients.
package grpc

import (
	"context"
	"fmt"

	authzv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/authz/v1"
	"git.dcentral.systems/toolz/goplt/pkg/registry"
	"git.dcentral.systems/toolz/goplt/pkg/services"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

// AuthzClient implements AuthzServiceClient using gRPC.
type AuthzClient struct {
	registry registry.ServiceRegistry
	conn     *grpc.ClientConn
	client   authzv1.AuthzServiceClient
}

// NewAuthzClient creates a new gRPC client for the Authz Service.
func NewAuthzClient(reg registry.ServiceRegistry) (services.AuthzServiceClient, error) {
	client := &AuthzClient{
		registry: reg,
	}
	return client, nil
}

// connect connects to the Authz Service.
func (c *AuthzClient) connect(ctx context.Context) error {
	if c.conn != nil {
		return nil
	}

	instances, err := c.registry.Discover(ctx, "authz-service")
	if err != nil {
		return fmt.Errorf("failed to discover authz service: %w", err)
	}

	if len(instances) == 0 {
		return fmt.Errorf("no instances found for authz-service")
	}

	instance := instances[0]
	address := fmt.Sprintf("%s:%d", instance.Address, instance.Port)

	conn, err := grpc.NewClient(address, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		return fmt.Errorf("failed to connect to authz-service at %s: %w", address, err)
	}

	c.conn = conn
	c.client = authzv1.NewAuthzServiceClient(conn)
	return nil
}

// Authorize checks if a user has a specific permission and returns an error if not.
func (c *AuthzClient) Authorize(ctx context.Context, userID, permission string) error {
	if err := c.connect(ctx); err != nil {
		return err
	}

	resp, err := c.client.Authorize(ctx, &authzv1.AuthorizeRequest{
		UserId:     userID,
		Permission: permission,
	})
	if err != nil {
		return fmt.Errorf("authorize failed: %w", err)
	}

	if !resp.Authorized {
		return fmt.Errorf("unauthorized: %s", resp.Message)
	}

	return nil
}

// HasPermission checks if a user has a specific permission.
func (c *AuthzClient) HasPermission(ctx context.Context, userID, permission string) (bool, error) {
	if err := c.connect(ctx); err != nil {
		return false, err
	}

	resp, err := c.client.HasPermission(ctx, &authzv1.HasPermissionRequest{
		UserId:     userID,
		Permission: permission,
	})
	if err != nil {
		return false, fmt.Errorf("has permission check failed: %w", err)
	}

	return resp.HasPermission, nil
}

// GetUserPermissions returns all permissions for a user.
func (c *AuthzClient) GetUserPermissions(ctx context.Context, userID string) ([]services.Permission, error) {
	if err := c.connect(ctx); err != nil {
		return nil, err
	}

	resp, err := c.client.GetUserPermissions(ctx, &authzv1.GetUserPermissionsRequest{
		UserId: userID,
	})
	if err != nil {
		return nil, fmt.Errorf("get user permissions failed: %w", err)
	}

	permissions := make([]services.Permission, 0, len(resp.Permissions))
	for _, p := range resp.Permissions {
		permissions = append(permissions, services.Permission{
			ID:          p.Id,
			Code:        p.Code,
			Name:        p.Name,
			Description: p.Description,
		})
	}

	return permissions, nil
}

// GetUserRoles returns all roles for a user.
func (c *AuthzClient) GetUserRoles(ctx context.Context, userID string) ([]services.Role, error) {
	if err := c.connect(ctx); err != nil {
		return nil, err
	}

	resp, err := c.client.GetUserRoles(ctx, &authzv1.GetUserRolesRequest{
		UserId: userID,
	})
	if err != nil {
		return nil, fmt.Errorf("get user roles failed: %w", err)
	}

	roles := make([]services.Role, 0, len(resp.Roles))
	for _, r := range resp.Roles {
		roles = append(roles, services.Role{
			ID:          r.Id,
			Name:        r.Name,
			Description: r.Description,
			Permissions: r.Permissions,
		})
	}

	return roles, nil
}
