package controllers

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
	"time"

	"fotbal-club/internal/models"
	"fotbal-club/pkg/logger"

	"github.com/gin-gonic/gin"
	"gorm.io/gorm"
)

type YouTubeController struct {
	DB *gorm.DB
}

func NewYouTubeController(db *gorm.DB) *YouTubeController {
	return &YouTubeController{DB: db}
}

type YouTubeVideo struct {
	VideoID        string `json:"video_id"`
	Title          string `json:"title"`
	ThumbnailURL   string `json:"thumbnail_url"`
	ViewsText      string `json:"views_text,omitempty"`
	Views          int64  `json:"views,omitempty"`
	PublishedText  string `json:"published_text,omitempty"`
	PublishedDate  string `json:"published_date,omitempty"` // YYYY-MM-DD
}

type YouTubeChannelPayload struct {
	Channel         string          `json:"channel"`
	ChannelURL      string          `json:"channel_url"`
	SubscribersText string          `json:"subscribers_text,omitempty"`
	Subscribers     int64           `json:"subscribers,omitempty"`
	Videos          []YouTubeVideo  `json:"videos"`
}

// GetYouTubeVideos returns cached YouTube channel videos
func (yc *YouTubeController) GetYouTubeVideos(c *gin.Context) {
	// Try to load from cache first
	cacheFile := filepath.Join("cache", "prefetch", "youtube_channel.json")
	if data, err := os.ReadFile(cacheFile); err == nil {
		var payload YouTubeChannelPayload
		if err := json.Unmarshal(data, &payload); err == nil && len(payload.Videos) > 0 {
			c.JSON(http.StatusOK, payload)
			return
		}
	}

	// If no cache, try to fetch and cache
	var settings models.Settings
	if err := yc.DB.First(&settings).Error; err == nil {
		youtubeURL := settings.YoutubeURL
		if youtubeURL == "" {
			c.JSON(http.StatusNoContent, gin.H{})
			return
		}

		// Fetch and cache
		payload, err := yc.fetchYouTubeChannel(youtubeURL)
		if err != nil {
			logger.Error("Failed to fetch YouTube channel: %v", err)
			c.JSON(http.StatusNoContent, gin.H{})
			return
		}

		// Save to cache
		if err := yc.saveYouTubeCache(payload); err != nil {
			logger.Warn("Failed to save YouTube cache: %v", err)
		}

		c.JSON(http.StatusOK, payload)
		return
	}

	c.JSON(http.StatusNoContent, gin.H{})
}

// fetchYouTubeChannel fetches videos from YouTube channel URL
func (yc *YouTubeController) fetchYouTubeChannel(channelURL string) (*YouTubeChannelPayload, error) {
	// Parse channel URL to extract channel handle or ID
	channelHandle := extractYouTubeHandle(channelURL)
	if channelHandle == "" {
		return nil, fmt.Errorf("invalid YouTube URL")
	}

	// Fetch the channel page HTML
	resp, err := http.Get(channelURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	html := string(body)

	// Parse channel data from HTML
	payload := &YouTubeChannelPayload{
		Channel:    channelHandle,
		ChannelURL: channelURL,
		Videos:     []YouTubeVideo{},
	}

	// Extract channel title
	if match := regexp.MustCompile(`<meta name="title" content="([^"]+)"`).FindStringSubmatch(html); len(match) > 1 {
		payload.Channel = match[1]
	}

	// Extract videos from initial data
	videos := parseYouTubeVideosFromHTML(html)
	payload.Videos = videos

	return payload, nil
}

// parseYouTubeVideosFromHTML extracts video data from YouTube HTML
func parseYouTubeVideosFromHTML(html string) []YouTubeVideo {
	videos := []YouTubeVideo{}

	// Find ytInitialData JSON
	re := regexp.MustCompile(`var ytInitialData = ({.+?});`)
	matches := re.FindStringSubmatch(html)
	if len(matches) < 2 {
		return videos
	}

	var data map[string]interface{}
	if err := json.Unmarshal([]byte(matches[1]), &data); err != nil {
		return videos
	}

	// Navigate to video items (this is a simplified version - YouTube's structure is complex)
	// Try to find gridRenderer or richItemRenderer
	tabs, ok := data["contents"].(map[string]interface{})
	if !ok {
		return videos
	}

	// Try multiple paths to find videos
	extractVideosRecursive(tabs, &videos, 0, 20) // limit recursion

	return videos
}

// extractVideosRecursive recursively searches for video data
func extractVideosRecursive(obj interface{}, videos *[]YouTubeVideo, depth, maxDepth int) {
	if depth > maxDepth || len(*videos) >= 20 {
		return
	}

	switch v := obj.(type) {
	case map[string]interface{}:
		// Check if this is a video renderer
		if videoId, ok := v["videoId"].(string); ok {
			video := YouTubeVideo{
				VideoID:      videoId,
				ThumbnailURL: fmt.Sprintf("https://i.ytimg.com/vi/%s/hqdefault.jpg", videoId),
			}

			// Extract title
			if title, ok := v["title"].(map[string]interface{}); ok {
				if runs, ok := title["runs"].([]interface{}); ok && len(runs) > 0 {
					if run, ok := runs[0].(map[string]interface{}); ok {
						if text, ok := run["text"].(string); ok {
							video.Title = text
						}
					}
				} else if simpleText, ok := title["simpleText"].(string); ok {
					video.Title = simpleText
				}
			}

			// Extract views
			if viewCountText, ok := v["viewCountText"].(map[string]interface{}); ok {
				if simpleText, ok := viewCountText["simpleText"].(string); ok {
					video.ViewsText = simpleText
					video.Views = parseViews(simpleText)
				}
			}

			// Extract published date
			if publishedTimeText, ok := v["publishedTimeText"].(map[string]interface{}); ok {
				if simpleText, ok := publishedTimeText["simpleText"].(string); ok {
					video.PublishedText = simpleText
					video.PublishedDate = estimateDate(simpleText)
				}
			}

			if video.Title != "" {
				*videos = append(*videos, video)
			}
		}

		// Recurse into nested objects
		for _, val := range v {
			extractVideosRecursive(val, videos, depth+1, maxDepth)
		}

	case []interface{}:
		for _, item := range v {
			extractVideosRecursive(item, videos, depth+1, maxDepth)
		}
	}
}

// extractYouTubeHandle extracts channel handle from URL
func extractYouTubeHandle(url string) string {
	url = strings.TrimSpace(url)
	if strings.Contains(url, "/@") {
		parts := strings.Split(url, "/@")
		if len(parts) > 1 {
			return strings.Split(parts[1], "/")[0]
		}
	}
	if strings.Contains(url, "/channel/") {
		parts := strings.Split(url, "/channel/")
		if len(parts) > 1 {
			return strings.Split(parts[1], "/")[0]
		}
	}
	return ""
}

// parseViews converts view count text to number
func parseViews(text string) int64 {
	text = strings.ToLower(text)
	text = strings.ReplaceAll(text, " ", "")
	text = strings.ReplaceAll(text, ",", "")

	var multiplier int64 = 1
	if strings.Contains(text, "k") {
		multiplier = 1000
		text = strings.ReplaceAll(text, "k", "")
	} else if strings.Contains(text, "m") {
		multiplier = 1000000
		text = strings.ReplaceAll(text, "m", "")
	} else if strings.Contains(text, "mil") {
		multiplier = 1000000
		text = strings.ReplaceAll(text, "mil", "")
	}

	// Extract first number
	re := regexp.MustCompile(`[\d.]+`)
	match := re.FindString(text)
	if match == "" {
		return 0
	}

	val, _ := strconv.ParseFloat(match, 64)
	return int64(val * float64(multiplier))
}

// estimateDate converts relative time text to YYYY-MM-DD
func estimateDate(text string) string {
	now := time.Now()
	text = strings.ToLower(text)

	if strings.Contains(text, "hour") || strings.Contains(text, "hodina") {
		return now.Format("2006-01-02")
	}
	if strings.Contains(text, "day") || strings.Contains(text, "den") || strings.Contains(text, "dní") {
		re := regexp.MustCompile(`(\d+)`)
		match := re.FindString(text)
		if match != "" {
			days, _ := strconv.Atoi(match)
			return now.AddDate(0, 0, -days).Format("2006-01-02")
		}
		return now.AddDate(0, 0, -1).Format("2006-01-02")
	}
	if strings.Contains(text, "week") || strings.Contains(text, "týden") {
		re := regexp.MustCompile(`(\d+)`)
		match := re.FindString(text)
		if match != "" {
			weeks, _ := strconv.Atoi(match)
			return now.AddDate(0, 0, -weeks*7).Format("2006-01-02")
		}
		return now.AddDate(0, 0, -7).Format("2006-01-02")
	}
	if strings.Contains(text, "month") || strings.Contains(text, "měsíc") {
		re := regexp.MustCompile(`(\d+)`)
		match := re.FindString(text)
		if match != "" {
			months, _ := strconv.Atoi(match)
			return now.AddDate(0, -months, 0).Format("2006-01-02")
		}
		return now.AddDate(0, -1, 0).Format("2006-01-02")
	}
	if strings.Contains(text, "year") || strings.Contains(text, "rok") {
		re := regexp.MustCompile(`(\d+)`)
		match := re.FindString(text)
		if match != "" {
			years, _ := strconv.Atoi(match)
			return now.AddDate(-years, 0, 0).Format("2006-01-02")
		}
		return now.AddDate(-1, 0, 0).Format("2006-01-02")
	}

	return now.Format("2006-01-02")
}

// saveYouTubeCache saves YouTube data to cache file
func (yc *YouTubeController) saveYouTubeCache(payload *YouTubeChannelPayload) error {
	cacheDir := filepath.Join("cache", "prefetch")
	if err := os.MkdirAll(cacheDir, 0755); err != nil {
		return err
	}

	cacheFile := filepath.Join(cacheDir, "youtube_channel.json")
	data, err := json.MarshalIndent(payload, "", "  ")
	if err != nil {
		return err
	}

	return os.WriteFile(cacheFile, data, 0644)
}
