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

import (
	"context"
	"fmt"

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

// AuditClient implements AuditServiceClient using gRPC.
type AuditClient struct {
	registry registry.ServiceRegistry
	conn     *grpc.ClientConn
	client   auditv1.AuditServiceClient
}

// NewAuditClient creates a new gRPC client for the Audit Service.
func NewAuditClient(reg registry.ServiceRegistry) (services.AuditServiceClient, error) {
	client := &AuditClient{
		registry: reg,
	}
	return client, nil
}

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

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

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

	c.conn = conn
	c.client = auditv1.NewAuditServiceClient(conn)
	return nil
}

// Record records an audit log entry.
func (c *AuditClient) Record(ctx context.Context, entry *services.AuditLogEntry) error {
	if err := c.connect(ctx); err != nil {
		return err
	}

	_, err := c.client.Record(ctx, &auditv1.RecordRequest{
		Entry: &auditv1.AuditLogEntry{
			UserId:     entry.UserID,
			Action:     entry.Action,
			Resource:   entry.Resource,
			ResourceId: entry.ResourceID,
			IpAddress:  entry.IPAddress,
			UserAgent:  entry.UserAgent,
			Metadata:   entry.Metadata,
			Timestamp:  entry.Timestamp,
		},
	})
	if err != nil {
		return fmt.Errorf("record audit log failed: %w", err)
	}

	return nil
}

// Query queries audit logs based on filters.
func (c *AuditClient) Query(ctx context.Context, filters *services.AuditLogFilters) ([]services.AuditLogEntry, error) {
	if err := c.connect(ctx); err != nil {
		return nil, err
	}

	req := &auditv1.QueryRequest{
		Limit:  int32(filters.Limit),
		Offset: int32(filters.Offset),
	}

	if filters.UserID != nil {
		req.UserId = filters.UserID
	}
	if filters.Action != nil {
		req.Action = filters.Action
	}
	if filters.Resource != nil {
		req.Resource = filters.Resource
	}
	if filters.ResourceID != nil {
		req.ResourceId = filters.ResourceID
	}
	if filters.StartTime != nil {
		req.StartTime = filters.StartTime
	}
	if filters.EndTime != nil {
		req.EndTime = filters.EndTime
	}

	resp, err := c.client.Query(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("query audit logs failed: %w", err)
	}

	entries := make([]services.AuditLogEntry, 0, len(resp.Entries))
	for _, e := range resp.Entries {
		entries = append(entries, services.AuditLogEntry{
			UserID:     e.UserId,
			Action:     e.Action,
			Resource:   e.Resource,
			ResourceID: e.ResourceId,
			IPAddress:  e.IpAddress,
			UserAgent:  e.UserAgent,
			Metadata:   e.Metadata,
			Timestamp:  e.Timestamp,
		})
	}

	return entries, nil
}
