package main

import (
	"fmt"
	"sync"
	"time"

	"github.com/shirou/gopsutil/mem"
)

// SearchResult is a generic interface for all types of search results.
type SearchResult interface{}

// Define various search result types implementing SearchResult interface
type TextSearchResult struct {
	URL         string
	Header      string
	Description string
	Source      string
}

type ImageSearchResult struct {
	ID         string
	Title      string
	Full       string // Full-size image URL
	Thumb      string // Thumbnail image URL
	ProxyFull  string // Proxied full-size image URL
	ProxyThumb string // Proxied thumbnail image URL (from cache)
	Source     string // Source webpage URL
	Width      int
	Height     int
}

type VideoResult struct {
	Href      string
	Title     string
	Date      string
	Views     string
	Creator   string
	Publisher string
	Image     string
	Duration  string
}

type TorrentResult struct {
	URL      string
	Seeders  int
	Leechers int
	Magnet   string
	Views    int
	Size     string
	Title    string
	Error    string
}

type ForumSearchResult struct {
	URL           string    `json:"url"`
	Header        string    `json:"header"`
	Description   string    `json:"description"`
	PublishedDate time.Time `json:"publishedDate"`
	ImgSrc        string    `json:"imgSrc,omitempty"`
	ThumbnailSrc  string    `json:"thumbnailSrc,omitempty"`
}

type MusicResult struct {
	URL           string
	Title         string
	Artist        string
	Description   string
	PublishedDate string
	Thumbnail     string
	//	AudioURL      string
	Source   string
	Duration string
}

// GeocodeCachedItem represents a geocoding result stored in the cache.
type GeocodeCachedItem struct {
	Latitude   string
	Longitude  string
	Found      bool
	StoredTime time.Time
}

// GeocodeCache is a thread-safe map for caching geocoding results.
type GeocodeCache struct {
	mu         sync.Mutex
	results    map[string]GeocodeCachedItem
	expiration time.Duration
}

var geocodeCache *GeocodeCache

// CacheKey represents the key used to store search results in the cache.
type CacheKey struct {
	Query string
	Page  int
	Safe  bool
	Lang  string
	Type  string
}

// CachedItem represents an item stored in the cache with an expiration time.
type CachedItem struct {
	Results    []SearchResult
	StoredTime time.Time
}

// ResultsCache is a thread-safe map for caching search results by composite keys.
type ResultsCache struct {
	mu         sync.Mutex
	results    map[string]CachedItem
	expiration time.Duration
}

var resultsCache *ResultsCache

// NewResultsCache creates a new ResultsCache with a specified expiration duration.
func NewResultsCache() *ResultsCache {
	printDebug("Initializing results cache with expiration: %s and max usage: %d bytes", config.RamCache.Duration, config.RamCache.MaxUsageBytes)
	return &ResultsCache{
		results:    make(map[string]CachedItem),
		expiration: config.RamCache.Duration,
	}
}

// NewGeocodeCache creates a new GeocodeCache with a specified expiration duration.
func NewGeocodeCache() *GeocodeCache {
	printDebug("Initializing geocode cache with expiration: %s", config.RamCache.Duration)
	return &GeocodeCache{
		results:    make(map[string]GeocodeCachedItem),
		expiration: config.RamCache.Duration,
	}
}

// Get retrieves the results for a given key from the cache.
func (rc *ResultsCache) Get(key CacheKey) ([]SearchResult, bool) {
	// Skip if RAM caching is disabled
	if !config.RamCacheEnabled {
		return nil, false
	}

	rc.mu.Lock()
	defer rc.mu.Unlock()

	item, exists := rc.results[rc.keyToString(key)]
	if !exists {
		return nil, false
	}

	// Check if the item has expired
	if time.Since(item.StoredTime) > config.RamCache.Duration {
		delete(rc.results, rc.keyToString(key))
		printDebug("Cache expired for key: %s", rc.keyToString(key))
		return nil, false
	}

	return item.Results, true
}

// Set stores the results for a given key in the cache.
func (rc *ResultsCache) Set(key CacheKey, results []SearchResult) {
	// Skip if RAM caching is disabled
	if !config.RamCacheEnabled {
		return
	}

	rc.mu.Lock()
	defer rc.mu.Unlock()

	if _, exists := rc.results[rc.keyToString(key)]; !exists {
		rc.results[rc.keyToString(key)] = CachedItem{
			Results:    results,
			StoredTime: time.Now(),
		}
		go rc.checkAndCleanCache()
	}
}

// keyToString converts a CacheKey to a string representation.
func (rc *ResultsCache) keyToString(key CacheKey) string {
	return fmt.Sprintf("%s|%d|%t|%s|%s", key.Query, key.Page, key.Safe, key.Lang, key.Type)
}

// checkAndCleanCache removes items if memory usage exceeds the limit.
func (rc *ResultsCache) checkAndCleanCache() {
	// Skip if RAM caching is disabled
	if !config.RamCacheEnabled {
		return
	}

	if rc.currentMemoryUsage() > config.RamCache.MaxUsageBytes {
		rc.cleanOldestItems()
	}
}

// currentMemoryUsage calculates the current memory usage in bytes.
func (rc *ResultsCache) currentMemoryUsage() uint64 {
	v, err := mem.VirtualMemory()
	if err != nil {
		printErr("Failed to get memory info: %v", err)
		return 0
	}
	return v.Used // Used memory in bytes
}

// Get retrieves the geocoding result for a given query from the cache.
func (gc *GeocodeCache) Get(query string) (latitude, longitude string, found bool, exists bool) {
	// Skip if RAM caching is disabled
	if !config.RamCacheEnabled {
		return "", "", false, false
	}

	gc.mu.Lock()
	defer gc.mu.Unlock()

	item, exists := gc.results[query]
	if !exists {
		return "", "", false, false
	}

	// Check if the item has expired
	if time.Since(item.StoredTime) > gc.expiration {
		delete(gc.results, query)
		printDebug("Geocode cache expired for query: %s", query)
		return "", "", false, false
	}

	return item.Latitude, item.Longitude, item.Found, true
}

func (gc *GeocodeCache) Set(query, latitude, longitude string, found bool) {
	// Skip if RAM caching is disabled
	if !config.RamCacheEnabled {
		return
	}

	gc.mu.Lock()
	defer gc.mu.Unlock()

	gc.results[query] = GeocodeCachedItem{
		Latitude:   latitude,
		Longitude:  longitude,
		Found:      found,
		StoredTime: time.Now(),
	}
}

func (rc *ResultsCache) cleanOldestItems() {
	rc.mu.Lock()
	defer rc.mu.Unlock()

	for rc.currentMemoryUsage() > config.RamCache.MaxUsageBytes {
		var oldestKey string
		var oldestTime time.Time = time.Now()

		for key, item := range rc.results {
			if item.StoredTime.Before(oldestTime) {
				oldestTime = item.StoredTime
				oldestKey = key
			}
		}

		if oldestKey != "" {
			delete(rc.results, oldestKey)
			printDebug("Removed oldest cache item: %s", oldestKey)
		} else {
			break
		}
	}
}

func convertToSearchResults(results interface{}) []SearchResult {
	switch res := results.(type) {
	case []TextSearchResult:
		genericResults := make([]SearchResult, len(res))
		for i, r := range res {
			genericResults[i] = r
		}
		return genericResults
	case []TorrentResult:
		genericResults := make([]SearchResult, len(res))
		for i, r := range res {
			genericResults[i] = r
		}
		return genericResults
	case []ImageSearchResult:
		genericResults := make([]SearchResult, len(res))
		for i, r := range res {
			genericResults[i] = r
		}
		return genericResults
	case []ForumSearchResult:
		genericResults := make([]SearchResult, len(res))
		for i, r := range res {
			genericResults[i] = r
		}
		return genericResults
	case []MusicResult:
		genericResults := make([]SearchResult, len(res))
		for i, r := range res {
			genericResults[i] = r
		}
		return genericResults
	}
	return nil
}

func convertToSpecificResults(results []SearchResult) ([]TextSearchResult, []TorrentResult, []ImageSearchResult, []ForumSearchResult, []MusicResult) {
	var textResults []TextSearchResult
	var torrentResults []TorrentResult
	var imageResults []ImageSearchResult
	var forumResults []ForumSearchResult
	var musicResults []MusicResult

	for _, r := range results {
		switch res := r.(type) {
		case TextSearchResult:
			textResults = append(textResults, res)
		case TorrentResult:
			torrentResults = append(torrentResults, res)
		case ImageSearchResult:
			imageResults = append(imageResults, res)
		case ForumSearchResult:
			forumResults = append(forumResults, res)
		case MusicResult:
			musicResults = append(musicResults, res)
		}
	}
	return textResults, torrentResults, imageResults, forumResults, musicResults
}