package heroagent

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"strconv"
	"time"

	"github.com/redis/go-redis/v9"
)

// RedisJobManager handles Redis operations for jobs
type RedisJobManager struct {
	client *redis.Client
	ctx    context.Context
}

// NewRedisJobManager creates a new Redis job manager
func NewRedisJobManager(tcpPort int, unixSocketPath string) (*RedisJobManager, error) {
	var client *redis.Client
	var err error

	// Try Unix socket first if provided
	if unixSocketPath != "" {
		log.Printf("Attempting to connect to Redis via Unix socket: %s", unixSocketPath)
		client = redis.NewClient(&redis.Options{
			Network:      "unix",
			Addr:         unixSocketPath,
			DB:           0,
			DialTimeout:  2 * time.Second,
			ReadTimeout:  5 * time.Second,
			WriteTimeout: 5 * time.Second,
		})

		// Test connection
		ctx := context.Background()
		_, pingErr := client.Ping(ctx).Result()
		if pingErr != nil {
			log.Printf("Failed to connect to Redis via Unix socket: %v, falling back to TCP", pingErr)
			// Close the failed client
			client.Close()
			err = pingErr // Update the outer err variable
		}
	}

	// If Unix socket connection failed or wasn't provided, use TCP
	if unixSocketPath == "" || err != nil {
		tcpAddr := fmt.Sprintf("localhost:%d", tcpPort)
		log.Printf("Connecting to Redis via TCP: %s", tcpAddr)
		client = redis.NewClient(&redis.Options{
			Network:      "tcp",
			Addr:         tcpAddr,
			DB:           0,
			DialTimeout:  5 * time.Second,
			ReadTimeout:  5 * time.Second,
			WriteTimeout: 5 * time.Second,
		})
	}

	// Test connection
	ctx := context.Background()
	_, pingErr := client.Ping(ctx).Result()
	if pingErr != nil {
		return nil, fmt.Errorf("failed to connect to Redis: %w", pingErr)
	}

	return &RedisJobManager{
		client: client,
		ctx:    ctx,
	}, nil
}

// Close closes the Redis client
func (r *RedisJobManager) Close() error {
	return r.client.Close()
}

// QueueKey returns the Redis queue key for a topic
func QueueKey(topic string) string {
	return fmt.Sprintf("heroqueue:%s", topic)
}

// StorageKey returns the Redis storage key for a job
func StorageKey(jobID uint32, topic string) string {
	return fmt.Sprintf("herojobs:%s:%d", topic, jobID)
}

// StoreJob stores a job in Redis
func (r *RedisJobManager) StoreJob(job *Job) error {
	// Convert job to JSON
	jobJSON, err := json.Marshal(job)
	if err != nil {
		return fmt.Errorf("failed to marshal job: %w", err)
	}

	// Store job in Redis
	storageKey := StorageKey(job.JobID, job.Topic)
	err = r.client.Set(r.ctx, storageKey, jobJSON, 0).Err()
	if err != nil {
		return fmt.Errorf("failed to store job in Redis: %w", err)
	}

	return nil
}

// EnqueueJob adds a job to its queue
func (r *RedisJobManager) EnqueueJob(job *Job) error {
	// Store the job first
	if err := r.StoreJob(job); err != nil {
		return err
	}

	// Add job ID to queue
	queueKey := QueueKey(job.Topic)
	err := r.client.RPush(r.ctx, queueKey, job.JobID).Err()
	if err != nil {
		return fmt.Errorf("failed to enqueue job: %w", err)
	}

	log.Printf("Job %d enqueued in Redis queue %s", job.JobID, queueKey)
	return nil
}

// GetJob retrieves a job from Redis
func (r *RedisJobManager) GetJob(jobID uint32, topic string) (*Job, error) {
	// Get job from Redis
	storageKey := StorageKey(jobID, topic)
	jobJSON, err := r.client.Get(r.ctx, storageKey).Result()
	if err != nil {
		if err == redis.Nil {
			return nil, fmt.Errorf("job not found: %d", jobID)
		}
		return nil, fmt.Errorf("failed to get job from Redis: %w", err)
	}

	// Parse job JSON
	job := &Job{}
	if err := json.Unmarshal([]byte(jobJSON), job); err != nil {
		return nil, fmt.Errorf("failed to unmarshal job: %w", err)
	}

	return job, nil
}

// DeleteJob deletes a job from Redis
func (r *RedisJobManager) DeleteJob(jobID uint32, topic string) error {
	// Delete job from Redis
	storageKey := StorageKey(jobID, topic)
	err := r.client.Del(r.ctx, storageKey).Err()
	if err != nil {
		return fmt.Errorf("failed to delete job from Redis: %w", err)
	}

	log.Printf("Job %d deleted from Redis", jobID)
	return nil
}

// FetchNextJob fetches the next job from a queue
func (r *RedisJobManager) FetchNextJob(topic string) (*Job, error) {
	queueKey := QueueKey(topic)

	// Get and remove first job ID from queue
	jobIDStr, err := r.client.LPop(r.ctx, queueKey).Result()
	if err != nil {
		if err == redis.Nil {
			return nil, fmt.Errorf("queue is empty")
		}
		return nil, fmt.Errorf("failed to fetch job ID from queue: %w", err)
	}

	// Convert job ID to uint32
	jobID, err := strconv.ParseUint(jobIDStr, 10, 32)
	if err != nil {
		return nil, fmt.Errorf("invalid job ID: %s", jobIDStr)
	}

	// Get job from Redis
	return r.GetJob(uint32(jobID), topic)
}

// ListQueues lists all job queues
func (r *RedisJobManager) ListQueues() ([]string, error) {
	// Get all queue keys
	queueKeys, err := r.client.Keys(r.ctx, "heroqueue:*").Result()
	if err != nil {
		return nil, fmt.Errorf("failed to list queues: %w", err)
	}

	// Extract topic names from queue keys
	topics := make([]string, 0, len(queueKeys))
	for _, queueKey := range queueKeys {
		// Extract topic from queue key (format: heroqueue:<topic>)
		topic := queueKey[10:] // Skip "heroqueue:"
		topics = append(topics, topic)
	}

	return topics, nil
}

// QueueSize returns the size of a queue
func (r *RedisJobManager) QueueSize(topic string) (int64, error) {
	queueKey := QueueKey(topic)

	// Get queue size
	size, err := r.client.LLen(r.ctx, queueKey).Result()
	if err != nil {
		return 0, fmt.Errorf("failed to get queue size: %w", err)
	}

	return size, nil
}

// UpdateJobStatus updates the status of a job in Redis
func (r *RedisJobManager) UpdateJobStatus(job *Job) error {
	// Update job in Redis
	return r.StoreJob(job)
}
