package controllers

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net/http"
	"strconv"
	"strings"
	"time"

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

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

type AnalyticsController struct {
	DB           *gorm.DB
	umamiService *services.UmamiService
}

func NewAnalyticsController(db *gorm.DB) *AnalyticsController {
	return &AnalyticsController{
		DB:           db,
		umamiService: services.NewUmamiService(),
	}
}

// resolveWebsiteID attempts to obtain a usable Umami website ID for requests
func (ac *AnalyticsController) resolveWebsiteID() (string, error) {
	if id := strings.TrimSpace(config.AppConfig.UmamiWebsiteID); id != "" {
		return id, nil
	}
	
	// Try to get the first available website
	id, err := ac.umamiService.GetDefaultWebsiteID()
	if err != nil {
		return "", err
	}
	
	config.AppConfig.UmamiWebsiteID = id
	return id, nil
}

// getClientIP extracts the real client IP address
func getClientIP(c *gin.Context) string {
	// Check X-Forwarded-For header
	xff := c.GetHeader("X-Forwarded-For")
	if xff != "" {
		ips := strings.Split(xff, ",")
		return strings.TrimSpace(ips[0])
	}
	
	// Check X-Real-IP header
	xri := c.GetHeader("X-Real-IP")
	if xri != "" {
		return xri
	}
	
	// Fall back to RemoteAddr
	return c.ClientIP()
}

// hashIP creates a privacy-preserving hash of IP address
func hashIP(ip string) string {
	// Add salt to make it harder to reverse
	salted := ip + "_fotbal_club_2025"
	hash := sha256.Sum256([]byte(salted))
	return hex.EncodeToString(hash[:])
}

// Track generic event (page view, click, etc.)
func (ac *AnalyticsController) TrackEvent(c *gin.Context) {
	var payload models.VisitorEvent
	if err := c.ShouldBindJSON(&payload); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Set client IP from request (hashed for privacy)
	realIP := getClientIP(c)
	payload.IPAddress = hashIP(realIP)
	payload.UserAgent = c.GetHeader("User-Agent")
	payload.Referrer = c.GetHeader("Referer")

	// Synchronize Page and PagePath fields
	if payload.PagePath != "" && payload.Page == "" {
		payload.Page = payload.PagePath
	} else if payload.Page != "" && payload.PagePath == "" {
		payload.PagePath = payload.Page
	}

	if err := ac.DB.Create(&payload).Error; err != nil {
		logger.Error("Failed to track event: %v", err)
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to track event"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"ok": true})
}

// GetAnalytics returns general analytics summary with users, events, and articles stats
func (ac *AnalyticsController) GetAnalytics(c *gin.Context) {
	// Get users stats
	var totalUsers int64
	ac.DB.Model(&models.User{}).Count(&totalUsers)
	
	// Get new users this week
	weekAgo := time.Now().AddDate(0, 0, -7)
	var newUsersThisWeek int64
	ac.DB.Model(&models.User{}).Where("created_at >= ?", weekAgo).Count(&newUsersThisWeek)
	
	// Get events stats
	var totalEvents int64
	ac.DB.Model(&models.Event{}).Count(&totalEvents)
	
	// Get upcoming events (events with start_time in the future)
	now := time.Now()
	var upcomingEvents int64
	ac.DB.Model(&models.Event{}).Where("start_time > ?", now).Count(&upcomingEvents)
	
	// Get articles stats
	var totalArticles int64
	ac.DB.Model(&models.Article{}).Count(&totalArticles)
	
	var publishedArticles int64
	ac.DB.Model(&models.Article{}).Where("published = ?", true).Count(&publishedArticles)
	
	c.JSON(http.StatusOK, gin.H{
		"users": gin.H{
			"total":          totalUsers,
			"new_this_week":  newUsersThisWeek,
		},
		"events": gin.H{
			"total":    totalEvents,
			"upcoming": upcomingEvents,
		},
		"articles": gin.H{
			"total":     totalArticles,
			"published": publishedArticles,
		},
	})
}

// GetVisitors returns visitor statistics grouped by day
func (ac *AnalyticsController) GetVisitors(c *gin.Context) {
	// Get parameters
	days := 30
	if d := c.Query("days"); d != "" {
		if parsed, err := strconv.Atoi(d); err == nil && parsed > 0 {
			days = parsed
		}
	}
	
	groupBy := c.DefaultQuery("groupBy", "day")
	startDate := time.Now().AddDate(0, 0, -days)
	
	type VisitorStat struct {
		Date           string `json:"date"`
		PageViews      int64  `json:"pageViews"`
		UniqueVisitors int64  `json:"uniqueVisitors"`
	}
	
	var stats []VisitorStat
	
	// Group by date
	if groupBy == "day" {
		ac.DB.Model(&models.VisitorEvent{}).
			Select("DATE(created_at) as date, COUNT(*) as page_views, COUNT(DISTINCT ip_address) as unique_visitors").
			Where("event_type = ? AND created_at >= ?", "page_view", startDate).
			Group("DATE(created_at)").
			Order("date ASC").
			Scan(&stats)
	} else {
		// Default to day grouping
		ac.DB.Model(&models.VisitorEvent{}).
			Select("DATE(created_at) as date, COUNT(*) as page_views, COUNT(DISTINCT ip_address) as unique_visitors").
			Where("event_type = ? AND created_at >= ?", "page_view", startDate).
			Group("DATE(created_at)").
			Order("date ASC").
			Scan(&stats)
	}
	
	// Transform data for chart format
	labels := make([]string, 0, len(stats))
	pageViewsData := make([]int64, 0, len(stats))
	uniqueVisitorsData := make([]int64, 0, len(stats))
	
	var totalVisitors int64
	for _, stat := range stats {
		// Format date as "d. M." (e.g., "5. 10.")
		t, err := time.Parse("2006-01-02", stat.Date)
		if err == nil {
			labels = append(labels, fmt.Sprintf("%d. %d.", t.Day(), int(t.Month())))
		} else {
			labels = append(labels, stat.Date)
		}
		pageViewsData = append(pageViewsData, stat.PageViews)
		uniqueVisitorsData = append(uniqueVisitorsData, stat.UniqueVisitors)
		totalVisitors += stat.UniqueVisitors
	}
	
	// Calculate change percentage (compare last 7 days with previous 7 days)
	var changePercentage float64
	if len(stats) >= 14 {
		var recentSum, previousSum int64
		for i := len(stats) - 7; i < len(stats); i++ {
			recentSum += stats[i].UniqueVisitors
		}
		for i := len(stats) - 14; i < len(stats)-7; i++ {
			previousSum += stats[i].UniqueVisitors
		}
		if previousSum > 0 {
			changePercentage = float64(recentSum-previousSum) / float64(previousSum) * 100
		}
	}
	
	response := gin.H{
		"totalVisitors": totalVisitors,
		"changePercentage": changePercentage,
		"chartData": gin.H{
			"labels": labels,
			"datasets": []gin.H{
				{
					"label": "Návštěvníci",
					"data": uniqueVisitorsData,
					"borderColor": "rgba(66, 153, 225, 1)",
					"backgroundColor": "rgba(66, 153, 225, 0.5)",
					"tension": 0.3,
					"fill": true,
				},
			},
		},
	}
	
	c.JSON(http.StatusOK, response)
}

// GetAnalyticsOverview returns overview statistics for admin dashboard
func (ac *AnalyticsController) GetAnalyticsOverview(c *gin.Context) {
	var totalPageViews, uniqueVisitors, pageViewsToday, pageViewsWeek, uniqueVisitorsWeek int64
	
	// Try to fetch from Umami first
	websiteID, err := ac.resolveWebsiteID()
	if err == nil && websiteID != "" {
		// Fetch overall stats (last 365 days for total)
		endAt := time.Now().Unix() * 1000
		startAt := time.Now().AddDate(-1, 0, 0).Unix() * 1000
		
		stats, err := ac.umamiService.GetWebsiteStats(websiteID, startAt, endAt)
		if err == nil {
			if pv, ok := stats["pageviews"].(map[string]interface{}); ok {
				if val, ok := pv["value"].(float64); ok {
					totalPageViews = int64(val)
				}
			}
			if v, ok := stats["visitors"].(map[string]interface{}); ok {
				if val, ok := v["value"].(float64); ok {
					uniqueVisitors = int64(val)
				}
			}
		}
		
		// Fetch today's stats
		todayStart := time.Now().Truncate(24 * time.Hour).Unix() * 1000
		todayEnd := time.Now().Unix() * 1000
		todayStats, err := ac.umamiService.GetWebsiteStats(websiteID, todayStart, todayEnd)
		if err == nil {
			if pv, ok := todayStats["pageviews"].(map[string]interface{}); ok {
				if val, ok := pv["value"].(float64); ok {
					pageViewsToday = int64(val)
				}
			}
		}
		
		// Fetch this week's stats
		weekStart := time.Now().AddDate(0, 0, -7).Unix() * 1000
		weekEnd := time.Now().Unix() * 1000
		weekStats, err := ac.umamiService.GetWebsiteStats(websiteID, weekStart, weekEnd)
		if err == nil {
			if pv, ok := weekStats["pageviews"].(map[string]interface{}); ok {
				if val, ok := pv["value"].(float64); ok {
					pageViewsWeek = int64(val)
				}
			}
			if v, ok := weekStats["visitors"].(map[string]interface{}); ok {
				if val, ok := v["value"].(float64); ok {
					uniqueVisitorsWeek = int64(val)
				}
			}
		}
	} else {
		// Fallback to internal analytics if Umami is not available
		ac.DB.Model(&models.VisitorEvent{}).Where("event_type = ?", "page_view").Count(&totalPageViews)
		ac.DB.Model(&models.VisitorEvent{}).Where("event_type = ?", "page_view").Distinct("ip_address").Count(&uniqueVisitors)
		
		today := time.Now().Format("2006-01-02")
		ac.DB.Model(&models.VisitorEvent{}).Where("event_type = ? AND DATE(created_at) = ?", "page_view", today).Count(&pageViewsToday)
		
		weekAgo := time.Now().AddDate(0, 0, -7)
		ac.DB.Model(&models.VisitorEvent{}).Where("event_type = ? AND created_at >= ?", "page_view", weekAgo).Count(&pageViewsWeek)
		ac.DB.Model(&models.VisitorEvent{}).Where("event_type = ? AND created_at >= ?", "page_view", weekAgo).Distinct("ip_address").Count(&uniqueVisitorsWeek)
	}

	// Total and published articles (always from DB)
	var totalArticles, publishedArticles int64
	ac.DB.Model(&models.Article{}).Count(&totalArticles)
	ac.DB.Model(&models.Article{}).Where("published = ?", true).Count(&publishedArticles)

	c.JSON(http.StatusOK, gin.H{
		"total_page_views":      totalPageViews,
		"unique_visitors":       uniqueVisitors,
		"total_articles":        totalArticles,
		"published_articles":    publishedArticles,
		"page_views_today":      pageViewsToday,
		"page_views_week":       pageViewsWeek,
		"unique_visitors_week":  uniqueVisitorsWeek,
	})
}

// GetTopPages returns the most visited pages
func (ac *AnalyticsController) GetTopPages(c *gin.Context) {
	limit := 10
	if l := c.Query("limit"); l != "" {
		fmt.Sscanf(l, "%d", &limit)
	}

	type PageStats struct {
		PagePath       string `json:"page_path"`
		PageName       string `json:"page_name"`
		ViewCount      int64  `json:"view_count"`
		UniqueVisitors int64  `json:"unique_visitors"`
	}

	var pages []PageStats
	
	// Try to fetch from Umami first
	websiteID, err := ac.resolveWebsiteID()
	if err == nil && websiteID != "" {
		// Fetch URL metrics from Umami (last 30 days)
		endAt := time.Now().Unix() * 1000
		startAt := time.Now().AddDate(0, 0, -30).Unix() * 1000
		
		metrics, err := ac.umamiService.GetWebsiteMetrics(websiteID, "url", startAt, endAt)
		if err == nil && metrics != nil {
			// Convert Umami metrics to PageStats format
			for i, metricMap := range metrics {
				if i >= limit {
					break
				}
				pagePath := ""
				viewCount := int64(0)
				
				if x, ok := metricMap["x"].(string); ok {
					pagePath = x
				}
				if y, ok := metricMap["y"].(float64); ok {
					viewCount = int64(y)
				}
				
				pages = append(pages, PageStats{
					PagePath:       pagePath,
					PageName:       pagePath,
					ViewCount:      viewCount,
					UniqueVisitors: viewCount, // Umami doesn't separate these
				})
			}
			c.JSON(http.StatusOK, pages)
			return
		}
	}
	
	// Fallback to internal analytics
	ac.DB.Model(&models.VisitorEvent{}).
		Where("event_type = ?", "page_view").
		Select("page_path, page_name, COUNT(*) as view_count, COUNT(DISTINCT ip_address) as unique_visitors").
		Group("page_path, page_name").
		Order("view_count DESC").
		Limit(limit).
		Scan(&pages)

	c.JSON(http.StatusOK, pages)
}

// GetTopArticles returns the most viewed articles
func (ac *AnalyticsController) GetTopArticles(c *gin.Context) {
	limit := 10
	if l := c.Query("limit"); l != "" {
		fmt.Sscanf(l, "%d", &limit)
	}

	var articles []models.Article
	ac.DB.Where("published = ?", true).
		Order("view_count DESC").
		Limit(limit).
		Find(&articles)

	c.JSON(http.StatusOK, articles)
}

type TopInteraction struct {
    Page    string `json:"page"`
    Element string `json:"element"`
    Count   int64  `json:"count"`
}

func (ctrl *AnalyticsController) GetTopInteractions(c *gin.Context) {
    daysParam := c.DefaultQuery("days", "30")
    limitParam := c.DefaultQuery("limit", "10")
    days, _ := strconv.Atoi(daysParam)
    if days <= 0 || days > 365 { days = 30 }
    limit, _ := strconv.Atoi(limitParam)
    if limit <= 0 || limit > 100 { limit = 10 }

    start := time.Now().AddDate(0, 0, -days)

    var rows []TopInteraction
    err := ctrl.DB.
        Model(&models.VisitorEvent{}).
        Select("page, element, COUNT(*) as count").
        Where("event_type IN ? AND created_at >= ?", []string{"click", "interaction"}, start).
        Group("page, element").
        Order("count DESC").
        Limit(limit).
        Scan(&rows).Error
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load interactions"})
        return
    }
    c.JSON(http.StatusOK, gin.H{"items": rows})
}
