package gobackend

import (
	"encoding/json"
	"sync"
	"time"
)

type DownloadProgress struct {
	CurrentFile   string  `json:"current_file"`
	Progress      float64 `json:"progress"`
	Speed         float64 `json:"speed_mbps"`
	BytesTotal    int64   `json:"bytes_total"`
	BytesReceived int64   `json:"bytes_received"`
	IsDownloading bool    `json:"is_downloading"`
	Status        string  `json:"status"`
}

type ItemProgress struct {
	ItemID        string  `json:"item_id"`
	BytesTotal    int64   `json:"bytes_total"`
	BytesReceived int64   `json:"bytes_received"`
	Progress      float64 `json:"progress"`
	SpeedMBps     float64 `json:"speed_mbps"`
	IsDownloading bool    `json:"is_downloading"`
	Status        string  `json:"status"`
}

type MultiProgress struct {
	Items map[string]*ItemProgress `json:"items"`
}

var (
	downloadDir   string
	downloadDirMu sync.RWMutex

	multiProgress = MultiProgress{Items: make(map[string]*ItemProgress)}
	multiMu       sync.RWMutex
)

func getProgress() DownloadProgress {
	multiMu.RLock()
	defer multiMu.RUnlock()

	for _, item := range multiProgress.Items {
		return DownloadProgress{
			CurrentFile:   item.ItemID,
			Progress:      item.Progress * 100,
			BytesTotal:    item.BytesTotal,
			BytesReceived: item.BytesReceived,
			IsDownloading: item.IsDownloading,
			Status:        item.Status,
		}
	}

	return DownloadProgress{}
}

func GetMultiProgress() string {
	multiMu.RLock()
	defer multiMu.RUnlock()

	jsonBytes, err := json.Marshal(multiProgress)
	if err != nil {
		return "{\"items\":{}}"
	}
	return string(jsonBytes)
}

func GetItemProgress(itemID string) string {
	multiMu.RLock()
	defer multiMu.RUnlock()

	if item, ok := multiProgress.Items[itemID]; ok {
		jsonBytes, _ := json.Marshal(item)
		return string(jsonBytes)
	}
	return "{}"
}

// StartItemProgress initializes progress tracking for an item
func StartItemProgress(itemID string) {
	multiMu.Lock()
	defer multiMu.Unlock()

	multiProgress.Items[itemID] = &ItemProgress{
		ItemID:        itemID,
		BytesTotal:    0,
		BytesReceived: 0,
		Progress:      0,
		IsDownloading: true,
		Status:        "downloading",
	}
}

// SetItemBytesTotal sets total bytes for an item
func SetItemBytesTotal(itemID string, total int64) {
	multiMu.Lock()
	defer multiMu.Unlock()

	if item, ok := multiProgress.Items[itemID]; ok {
		item.BytesTotal = total
	}
}

// SetItemBytesReceived sets bytes received for an item
func SetItemBytesReceived(itemID string, received int64) {
	multiMu.Lock()
	defer multiMu.Unlock()

	if item, ok := multiProgress.Items[itemID]; ok {
		item.BytesReceived = received
		if item.BytesTotal > 0 {
			item.Progress = float64(received) / float64(item.BytesTotal)
		}
	}
}

// SetItemBytesReceivedWithSpeed sets bytes received and speed for an item
func SetItemBytesReceivedWithSpeed(itemID string, received int64, speedMBps float64) {
	multiMu.Lock()
	defer multiMu.Unlock()

	if item, ok := multiProgress.Items[itemID]; ok {
		item.BytesReceived = received
		item.SpeedMBps = speedMBps
		if item.BytesTotal > 0 {
			item.Progress = float64(received) / float64(item.BytesTotal)
		}
	}
}

// CompleteItemProgress marks an item as complete
func CompleteItemProgress(itemID string) {
	multiMu.Lock()
	defer multiMu.Unlock()

	if item, ok := multiProgress.Items[itemID]; ok {
		item.Progress = 1.0
		item.IsDownloading = false
		item.Status = "completed"
	}
}

// SetItemProgress sets progress for an item directly
func SetItemProgress(itemID string, progress float64, bytesReceived, bytesTotal int64) {
	multiMu.Lock()
	defer multiMu.Unlock()

	if item, ok := multiProgress.Items[itemID]; ok {
		item.Progress = progress
		if bytesReceived > 0 {
			item.BytesReceived = bytesReceived
		}
		if bytesTotal > 0 {
			item.BytesTotal = bytesTotal
		}
	}
}

// SetItemFinalizing marks an item as finalizing (embedding metadata)
func SetItemFinalizing(itemID string) {
	multiMu.Lock()
	defer multiMu.Unlock()

	if item, ok := multiProgress.Items[itemID]; ok {
		item.Progress = 1.0
		item.Status = "finalizing"
	}
}

// RemoveItemProgress removes progress tracking for an item
func RemoveItemProgress(itemID string) {
	multiMu.Lock()
	defer multiMu.Unlock()

	delete(multiProgress.Items, itemID)
}

// ClearAllItemProgress clears all item progress
func ClearAllItemProgress() {
	multiMu.Lock()
	defer multiMu.Unlock()

	multiProgress.Items = make(map[string]*ItemProgress)
}

// setDownloadDir sets the default download directory
func setDownloadDir(path string) error {
	downloadDirMu.Lock()
	defer downloadDirMu.Unlock()
	downloadDir = path
	return nil
}

// ItemProgressWriter wraps io.Writer to track download progress for a specific item
type ItemProgressWriter struct {
	writer       interface{ Write([]byte) (int, error) }
	itemID       string
	current      int64
	lastReported int64     // Track last reported bytes for threshold-based updates
	startTime    time.Time // Track start time for speed calculation
	lastTime     time.Time // Track last update time for speed calculation
	lastBytes    int64     // Track bytes at last speed calculation
}

const progressUpdateThreshold = 64 * 1024 // Update progress every 64KB

// NewItemProgressWriter creates a new progress writer for a specific item
func NewItemProgressWriter(w interface{ Write([]byte) (int, error) }, itemID string) *ItemProgressWriter {
	now := time.Now()
	return &ItemProgressWriter{
		writer:       w,
		itemID:       itemID,
		current:      0,
		lastReported: 0,
		startTime:    now,
		lastTime:     now,
		lastBytes:    0,
	}
}

// Write implements io.Writer with threshold-based progress updates and speed tracking
func (pw *ItemProgressWriter) Write(p []byte) (int, error) {
	if pw.itemID != "" && isDownloadCancelled(pw.itemID) {
		return 0, ErrDownloadCancelled
	}
	n, err := pw.writer.Write(p)
	if err != nil {
		return n, err
	}
	pw.current += int64(n)

	if pw.lastReported == 0 || pw.current-pw.lastReported >= progressUpdateThreshold {
		now := time.Now()
		elapsed := now.Sub(pw.lastTime).Seconds()
		var speedMBps float64
		if elapsed > 0 {
			bytesInInterval := pw.current - pw.lastBytes
			speedMBps = float64(bytesInInterval) / (1024 * 1024) / elapsed
		}

		SetItemBytesReceivedWithSpeed(pw.itemID, pw.current, speedMBps)
		pw.lastReported = pw.current
		pw.lastTime = now
		pw.lastBytes = pw.current
	}
	return n, nil
}
