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

import (
	"context"
	"fmt"

	identityv1 "git.dcentral.systems/toolz/goplt/api/proto/generated/identity/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"
)

// IdentityClient implements IdentityServiceClient using gRPC.
type IdentityClient struct {
	registry registry.ServiceRegistry
	conn     *grpc.ClientConn
	client   identityv1.IdentityServiceClient
}

// NewIdentityClient creates a new gRPC client for the Identity Service.
func NewIdentityClient(reg registry.ServiceRegistry) (services.IdentityServiceClient, error) {
	client := &IdentityClient{
		registry: reg,
	}
	return client, nil
}

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

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

	if len(instances) == 0 {
		return fmt.Errorf("no instances found for identity-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 identity-service at %s: %w", address, err)
	}

	c.conn = conn
	c.client = identityv1.NewIdentityServiceClient(conn)
	return nil
}

// GetUser retrieves a user by ID.
func (c *IdentityClient) GetUser(ctx context.Context, id string) (*services.User, error) {
	if err := c.connect(ctx); err != nil {
		return nil, err
	}

	resp, err := c.client.GetUser(ctx, &identityv1.GetUserRequest{Id: id})
	if err != nil {
		return nil, fmt.Errorf("get user failed: %w", err)
	}

	return protoUserToServiceUser(resp.User), nil
}

// GetUserByEmail retrieves a user by email address.
func (c *IdentityClient) GetUserByEmail(ctx context.Context, email string) (*services.User, error) {
	if err := c.connect(ctx); err != nil {
		return nil, err
	}

	resp, err := c.client.GetUserByEmail(ctx, &identityv1.GetUserByEmailRequest{Email: email})
	if err != nil {
		return nil, fmt.Errorf("get user by email failed: %w", err)
	}

	return protoUserToServiceUser(resp.User), nil
}

// CreateUser creates a new user.
func (c *IdentityClient) CreateUser(ctx context.Context, user *services.CreateUserRequest) (*services.User, error) {
	if err := c.connect(ctx); err != nil {
		return nil, err
	}

	resp, err := c.client.CreateUser(ctx, &identityv1.CreateUserRequest{
		Email:     user.Email,
		Username:  user.Username,
		Password:  user.Password,
		FirstName: user.FirstName,
		LastName:  user.LastName,
	})
	if err != nil {
		return nil, fmt.Errorf("create user failed: %w", err)
	}

	return protoUserToServiceUser(resp.User), nil
}

// UpdateUser updates an existing user.
func (c *IdentityClient) UpdateUser(ctx context.Context, id string, user *services.UpdateUserRequest) (*services.User, error) {
	if err := c.connect(ctx); err != nil {
		return nil, err
	}

	var email, username, firstName, lastName *string
	if user.Email != nil && *user.Email != "" {
		email = user.Email
	}
	if user.Username != nil && *user.Username != "" {
		username = user.Username
	}
	if user.FirstName != nil && *user.FirstName != "" {
		firstName = user.FirstName
	}
	if user.LastName != nil && *user.LastName != "" {
		lastName = user.LastName
	}

	resp, err := c.client.UpdateUser(ctx, &identityv1.UpdateUserRequest{
		Id:        id,
		Email:     email,
		Username:  username,
		FirstName: firstName,
		LastName:  lastName,
	})
	if err != nil {
		return nil, fmt.Errorf("update user failed: %w", err)
	}

	return protoUserToServiceUser(resp.User), nil
}

// DeleteUser deletes a user.
func (c *IdentityClient) DeleteUser(ctx context.Context, id string) error {
	if err := c.connect(ctx); err != nil {
		return err
	}

	_, err := c.client.DeleteUser(ctx, &identityv1.DeleteUserRequest{Id: id})
	if err != nil {
		return fmt.Errorf("delete user failed: %w", err)
	}

	return nil
}

// VerifyEmail verifies a user's email address using a verification token.
func (c *IdentityClient) VerifyEmail(ctx context.Context, token string) error {
	if err := c.connect(ctx); err != nil {
		return err
	}

	_, err := c.client.VerifyEmail(ctx, &identityv1.VerifyEmailRequest{Token: token})
	if err != nil {
		return fmt.Errorf("verify email failed: %w", err)
	}

	return nil
}

// RequestPasswordReset requests a password reset token.
func (c *IdentityClient) RequestPasswordReset(ctx context.Context, email string) error {
	if err := c.connect(ctx); err != nil {
		return err
	}

	_, err := c.client.RequestPasswordReset(ctx, &identityv1.RequestPasswordResetRequest{Email: email})
	if err != nil {
		return fmt.Errorf("request password reset failed: %w", err)
	}

	return nil
}

// ResetPassword resets a user's password using a reset token.
func (c *IdentityClient) ResetPassword(ctx context.Context, token, newPassword string) error {
	if err := c.connect(ctx); err != nil {
		return err
	}

	_, err := c.client.ResetPassword(ctx, &identityv1.ResetPasswordRequest{
		Token:       token,
		NewPassword: newPassword,
	})
	if err != nil {
		return fmt.Errorf("reset password failed: %w", err)
	}

	return nil
}

// protoUserToServiceUser converts a proto User to a service User.
func protoUserToServiceUser(u *identityv1.User) *services.User {
	if u == nil {
		return nil
	}
	return &services.User{
		ID:            u.Id,
		Email:         u.Email,
		Username:      u.Username,
		FirstName:     u.FirstName,
		LastName:      u.LastName,
		EmailVerified: u.EmailVerified,
		CreatedAt:     u.CreatedAt,
		UpdatedAt:     u.UpdatedAt,
	}
}
