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

import (
	"context"
	"fmt"

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

// AuthClient implements AuthServiceClient using gRPC.
type AuthClient struct {
	registry registry.ServiceRegistry
	conn     *grpc.ClientConn
	client   authv1.AuthServiceClient
}

// NewAuthClient creates a new gRPC client for the Auth Service.
func NewAuthClient(reg registry.ServiceRegistry) (services.AuthServiceClient, error) {
	client := &AuthClient{
		registry: reg,
	}
	return client, nil
}

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

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

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

	c.conn = conn
	c.client = authv1.NewAuthServiceClient(conn)
	return nil
}

// Login authenticates a user and returns access and refresh tokens.
func (c *AuthClient) Login(ctx context.Context, email, password string) (*services.TokenResponse, error) {
	if err := c.connect(ctx); err != nil {
		return nil, err
	}

	resp, err := c.client.Login(ctx, &authv1.LoginRequest{
		Email:    email,
		Password: password,
	})
	if err != nil {
		return nil, fmt.Errorf("login failed: %w", err)
	}

	return &services.TokenResponse{
		AccessToken:  resp.AccessToken,
		RefreshToken: resp.RefreshToken,
		ExpiresIn:    resp.ExpiresIn,
		TokenType:    resp.TokenType,
	}, nil
}

// RefreshToken refreshes an access token using a refresh token.
func (c *AuthClient) RefreshToken(ctx context.Context, refreshToken string) (*services.TokenResponse, error) {
	if err := c.connect(ctx); err != nil {
		return nil, err
	}

	resp, err := c.client.RefreshToken(ctx, &authv1.RefreshTokenRequest{
		RefreshToken: refreshToken,
	})
	if err != nil {
		return nil, fmt.Errorf("refresh token failed: %w", err)
	}

	return &services.TokenResponse{
		AccessToken:  resp.AccessToken,
		RefreshToken: resp.RefreshToken,
		ExpiresIn:    resp.ExpiresIn,
		TokenType:    resp.TokenType,
	}, nil
}

// ValidateToken validates a JWT token and returns the token claims.
func (c *AuthClient) ValidateToken(ctx context.Context, token string) (*services.TokenClaims, error) {
	if err := c.connect(ctx); err != nil {
		return nil, err
	}

	resp, err := c.client.ValidateToken(ctx, &authv1.ValidateTokenRequest{
		Token: token,
	})
	if err != nil {
		return nil, fmt.Errorf("validate token failed: %w", err)
	}

	return &services.TokenClaims{
		UserID:    resp.UserId,
		Email:     resp.Email,
		Roles:     resp.Roles,
		ExpiresAt: resp.ExpiresAt,
	}, nil
}

// Logout invalidates a refresh token.
func (c *AuthClient) Logout(ctx context.Context, refreshToken string) error {
	if err := c.connect(ctx); err != nil {
		return err
	}

	_, err := c.client.Logout(ctx, &authv1.LogoutRequest{
		RefreshToken: refreshToken,
	})
	if err != nil {
		return fmt.Errorf("logout failed: %w", err)
	}

	return nil
}
