package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"sync"
	"time"
)

const retryDuration = 12 * time.Hour // Retry duration for unresponding piped instances

var (
	pipedInstances    = []string{}
	disabledInstances = make(map[string]bool)
	mu                sync.Mutex
	videoResultsChan  = make(chan []VideoResult) // Channel to receive video results from other nodes
)

func initPipedInstances() {
	pipedInstances = config.MetaSearch.Video
}

// VideoAPIResponse matches the structure of the JSON response from the Piped API
type VideoAPIResponse struct {
	Items []struct {
		URL          string `json:"url"`
		Title        string `json:"title"`
		UploaderName string `json:"uploaderName"`
		Views        int    `json:"views"`
		Thumbnail    string `json:"thumbnail"`
		Duration     int    `json:"duration"`
		UploadedDate string `json:"uploadedDate"`
		Type         string `json:"type"`
	} `json:"items"`
}

// Function to format views similarly to the Python code
func formatViews(views int) string {
	switch {
	case views >= 1_000_000_000:
		return fmt.Sprintf("%.1fB views", float64(views)/1_000_000_000)
	case views >= 1_000_000:
		return fmt.Sprintf("%.1fM views", float64(views)/1_000_000)
	case views >= 10_000:
		return fmt.Sprintf("%.1fK views", float64(views)/1_000)
	case views == 1:
		return fmt.Sprintf("%d view", views)
	default:
		return fmt.Sprintf("%d views", views)
	}
}

// formatDuration formats video duration as done in the Python code
func formatDuration(seconds int) string {
	if 0 > seconds {
		return "Live"
	}

	hours := seconds / 3600
	minutes := (seconds % 3600) / 60
	seconds = seconds % 60

	if hours > 0 {
		return fmt.Sprintf("%02d:%02d:%02d", hours, minutes, seconds)
	}
	return fmt.Sprintf("%02d:%02d", minutes, seconds)
}

func init() {
	go checkDisabledInstancesPeriodically()
}

func checkDisabledInstancesPeriodically() {
	checkAndReactivateInstances() // Initial immediate check
	ticker := time.NewTicker(retryDuration)
	defer ticker.Stop()

	for range ticker.C {
		checkAndReactivateInstances()
	}
}

func checkAndReactivateInstances() {
	mu.Lock()
	defer mu.Unlock()

	for instance, isDisabled := range disabledInstances {
		if isDisabled {
			// Check if the instance is available again
			if testInstanceAvailability(instance) {
				printInfo("Instance %s is now available and reactivated.", instance)
				delete(disabledInstances, instance)
			} else {
				printInfo("Instance %s is still not available.", instance)
			}
		}
	}
}

func testInstanceAvailability(instance string) bool {
	resp, err := http.Get(fmt.Sprintf("https://%s/search?q=%s&filter=all", instance, url.QueryEscape("test")))
	if err != nil || resp.StatusCode != http.StatusOK {
		return false
	}
	return true
}

func makeHTMLRequest(query, safe, lang string, page int) (*VideoAPIResponse, error) {
	var lastError error
	mu.Lock()
	defer mu.Unlock()

	for _, instance := range pipedInstances {
		if disabledInstances[instance] {
			continue // Skip this instance because it's still disabled
		}

		url := fmt.Sprintf("https://%s/search?q=%s&filter=all&safe=%s&lang=%s&page=%d", instance, url.QueryEscape(query), safe, lang, page)
		resp, err := http.Get(url)
		if err != nil || resp.StatusCode != http.StatusOK {
			printInfo("Disabling instance %s due to error or status code: %v", instance, err)
			disabledInstances[instance] = true
			lastError = fmt.Errorf("error making request to %s: %w", instance, err)
			continue
		}

		defer resp.Body.Close()
		var apiResp VideoAPIResponse
		if err := json.NewDecoder(resp.Body).Decode(&apiResp); err != nil {
			lastError = fmt.Errorf("error decoding response from %s: %w", instance, err)
			continue
		}
		return &apiResp, nil
	}
	return nil, fmt.Errorf("all instances failed, last error: %v", lastError)
}

// handleVideoSearch adapted from the Python `videoResults`, handles video search requests
func handleVideoSearch(w http.ResponseWriter, settings UserSettings, query string, page int) {
	start := time.Now()

	var results []VideoResult
	if config.MetaSearchEnabled {
		results = fetchVideoResults(query, settings.SafeSearch, settings.SearchLanguage, page)
	}

	if len(results) == 0 {
		printWarn("No results from primary search, trying other nodes")
		results = tryOtherNodesForVideoSearch(query, settings.SafeSearch, settings.SearchLanguage, page, []string{hostID})
	}

	elapsed := time.Since(start)

	// Prepare the data to pass to the template
	data := map[string]interface{}{
		"Results":         results,
		"Query":           query,
		"Fetched":         fmt.Sprintf("%.2f %s", elapsed.Seconds(), Translate("seconds")),
		"Page":            page,
		"HasPrevPage":     page > 1,
		"HasNextPage":     len(results) > 0,
		"NoResults":       len(results) == 0,
		"LanguageOptions": languageOptions,
		"CurrentLang":     settings.SearchLanguage,
		"Theme":           settings.Theme,
		"Safe":            settings.SafeSearch,
		"IsThemeDark":     settings.IsThemeDark,
	}

	// Render the template without measuring time
	renderTemplate(w, "videos.html", data)
}

func fetchVideoResults(query, safe, lang string, page int) []VideoResult {
	// Check if the crawler is enabled
	if !config.MetaSearchEnabled {
		printDebug("Crawler is disabled; skipping video search.")
		return []VideoResult{}
	}

	// Proceed with Piped API request if MetaSearchEnabled
	apiResp, err := makeHTMLRequest(query, safe, lang, page)
	if err != nil {
		printWarn("Error fetching video results: %v", err)
		return nil
	}

	var results []VideoResult
	for _, item := range apiResp.Items {
		if item.Type == "channel" || item.Type == "playlist" {
			continue
		}
		if item.UploadedDate == "" {
			item.UploadedDate = "Now"
		}

		results = append(results, VideoResult{
			Href:      fmt.Sprintf("https://youtube.com%s", item.URL),
			Title:     item.Title,
			Date:      item.UploadedDate,
			Views:     formatViews(item.Views),
			Creator:   item.UploaderName,
			Publisher: "Piped",
			Image:     item.Thumbnail, //fmt.Sprintf("/img_proxy?url=%s", url.QueryEscape(item.Thumbnail)), // Using image proxy is not working, but its not needed here as piped is proxy anyway
			Duration:  formatDuration(item.Duration),
		})
	}
	return results
}