package cache

import (
	"context"
	"fmt"
	"time"
)

// DownloadProgress represents real-time download progress
type DownloadProgress struct {
	JobID             string    `json:"jobId"`
	Status            string    `json:"status"`
	ProgressPercent   int       `json:"progressPercent"`
	BytesTotal        int64     `json:"bytesTotal"`
	BytesDownloaded   int64     `json:"bytesDownloaded"`
	DownloadSpeedMbps float64   `json:"downloadSpeedMbps"`
	EtaSeconds        int       `json:"etaSeconds"`
	UpdatedAt         time.Time `json:"updatedAt"`
}

// DownloadCache provides caching for download operations
type DownloadCache struct {
	service *Service
	keys    *KeyBuilder
}

// NewDownloadCache creates a new download cache
func NewDownloadCache(service *Service, namespace string) *DownloadCache {
	return &DownloadCache{
		service: service,
		keys:    NewKeyBuilder(namespace),
	}
}

// SetProgress stores download progress in cache
func (dc *DownloadCache) SetProgress(ctx context.Context, progress DownloadProgress) error {
	key := dc.keys.DownloadJobKey(progress.JobID)
	progress.UpdatedAt = time.Now().UTC()
	return dc.service.Set(ctx, key, progress, TTLDownload)
}

// GetProgress retrieves download progress from cache
func (dc *DownloadCache) GetProgress(ctx context.Context, jobID string) (*DownloadProgress, error) {
	key := dc.keys.DownloadJobKey(jobID)
	var progress DownloadProgress

	if err := dc.service.Get(ctx, key, &progress); err != nil {
		return nil, err
	}

	return &progress, nil
}

// DeleteProgress removes download progress from cache
func (dc *DownloadCache) DeleteProgress(ctx context.Context, jobID string) error {
	key := dc.keys.DownloadJobKey(jobID)
	return dc.service.Delete(ctx, key)
}

// GetUserDownloads retrieves cached download list for a user
func (dc *DownloadCache) GetUserDownloads(ctx context.Context, userID, status string, target interface{}) error {
	key := dc.keys.DownloadListKey(userID, status)
	return dc.service.Get(ctx, key, target)
}

// SetUserDownloads stores download list in cache
func (dc *DownloadCache) SetUserDownloads(ctx context.Context, userID, status string, data interface{}) error {
	key := dc.keys.DownloadListKey(userID, status)
	return dc.service.Set(ctx, key, data, TTLDownload)
}

// InvalidateUserDownloads removes cached download list
func (dc *DownloadCache) InvalidateUserDownloads(ctx context.Context, userID string) error {
	// Invalidate all status variations
	statuses := []string{"", "queued", "preparing", "downloading", "completed", "failed", "cancelled"}
	keys := make([]string, 0, len(statuses))

	for _, status := range statuses {
		keys = append(keys, dc.keys.DownloadListKey(userID, status))
	}

	return dc.service.Delete(ctx, keys...)
}

// UpdateProgressField updates a specific field of download progress
func (dc *DownloadCache) UpdateProgressField(ctx context.Context, jobID string, updateFunc func(*DownloadProgress)) error {
	progress, err := dc.GetProgress(ctx, jobID)
	if err != nil {
		if err == ErrCacheMiss {
			// Create new progress entry
			progress = &DownloadProgress{
				JobID:     jobID,
				UpdatedAt: time.Now().UTC(),
			}
		} else {
			return err
		}
	}

	updateFunc(progress)
	return dc.SetProgress(ctx, *progress)
}

// IncrementDownloadedBytes atomically increments downloaded bytes
func (dc *DownloadCache) IncrementDownloadedBytes(ctx context.Context, jobID string, bytes int64) error {
	return dc.UpdateProgressField(ctx, jobID, func(p *DownloadProgress) {
		p.BytesDownloaded += bytes
		if p.BytesTotal > 0 {
			p.ProgressPercent = int((p.BytesDownloaded * 100) / p.BytesTotal)
		}
	})
}

// SetDownloadSpeed updates the download speed
func (dc *DownloadCache) SetDownloadSpeed(ctx context.Context, jobID string, speedMbps float64) error {
	return dc.UpdateProgressField(ctx, jobID, func(p *DownloadProgress) {
		p.DownloadSpeedMbps = speedMbps

		// Calculate ETA if we have speed and remaining bytes
		if speedMbps > 0 && p.BytesTotal > 0 {
			remainingBytes := p.BytesTotal - p.BytesDownloaded
			if remainingBytes > 0 {
				// Convert Mbps to bytes per second
				bytesPerSecond := (speedMbps * 1024 * 1024) / 8
				p.EtaSeconds = int(float64(remainingBytes) / bytesPerSecond)
			}
		}
	})
}

// GetActiveDownloads retrieves all active download jobs
func (dc *DownloadCache) GetActiveDownloads(ctx context.Context) ([]DownloadProgress, error) {
	pattern := dc.keys.Build(PrefixDownload, "job", "*")
	keys, err := dc.service.Keys(ctx, pattern)
	if err != nil {
		return nil, err
	}

	downloads := make([]DownloadProgress, 0, len(keys))
	for _, key := range keys {
		var progress DownloadProgress
		if err := dc.service.Get(ctx, key, &progress); err != nil {
			continue
		}

		// Only include active downloads
		if progress.Status == "downloading" || progress.Status == "preparing" {
			downloads = append(downloads, progress)
		}
	}

	return downloads, nil
}

// CleanupStaleProgress removes progress entries that haven't been updated recently
func (dc *DownloadCache) CleanupStaleProgress(ctx context.Context, maxAge time.Duration) error {
	pattern := dc.keys.Build(PrefixDownload, "job", "*")
	keys, err := dc.service.Keys(ctx, pattern)
	if err != nil {
		return err
	}

	now := time.Now().UTC()
	toDelete := make([]string, 0)

	for _, key := range keys {
		var progress DownloadProgress
		if err := dc.service.Get(ctx, key, &progress); err != nil {
			continue
		}

		if now.Sub(progress.UpdatedAt) > maxAge {
			toDelete = append(toDelete, key)
		}
	}

	if len(toDelete) > 0 {
		return dc.service.Delete(ctx, toDelete...)
	}

	return nil
}

// BulkSetProgress stores multiple download progress entries at once
func (dc *DownloadCache) BulkSetProgress(ctx context.Context, progressList []DownloadProgress) error {
	if len(progressList) == 0 {
		return nil
	}

	pairs := make(map[string]interface{}, len(progressList))
	for _, progress := range progressList {
		key := dc.keys.DownloadJobKey(progress.JobID)
		progress.UpdatedAt = time.Now().UTC()
		pairs[key] = progress
	}

	if err := dc.service.MSet(ctx, pairs); err != nil {
		return err
	}

	// Set TTL for each key
	for key := range pairs {
		if err := dc.service.Expire(ctx, key, TTLDownload); err != nil {
			return fmt.Errorf("failed to set TTL for %s: %w", key, err)
		}
	}

	return nil
}
