package controllers

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

	"fotbal-club/internal/models"

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

type PollController struct {
	DB *gorm.DB
}

func NewPollController(db *gorm.DB) *PollController {
	return &PollController{DB: db}
}

// GetPolls returns list of polls (admin or public)
func (pc *PollController) GetPolls(c *gin.Context) {
	var polls []models.Poll
	query := pc.DB.Preload("Options").Preload("Category").Preload("Creator").
		Preload("RelatedArticle").Preload("RelatedEvent")
	
	// Check if admin request
	if c.GetBool("isAdmin") {
		// Admin sees all polls
		status := c.Query("status")
		if status != "" {
			query = query.Where("status = ?", status)
		}
	} else {
		// Public sees only active polls
		query = query.Where("status = ?", "active")
		now := time.Now()
		query = query.Where("(start_date IS NULL OR start_date <= ?) AND (end_date IS NULL OR end_date >= ?)", now, now)
	}
	
	// Featured filter
	if c.Query("featured") == "true" {
		query = query.Where("featured = ?", true)
	}
	
	// Filter by relationships
	if articleID := c.Query("article_id"); articleID != "" {
		query = query.Where("related_article_id = ?", articleID)
	}
	if eventID := c.Query("event_id"); eventID != "" {
		query = query.Where("related_event_id = ?", eventID)
	}
	if videoURL := c.Query("video_url"); videoURL != "" {
		query = query.Where("related_video_url = ?", videoURL)
	}
	
	// Order
	query = query.Order("created_at DESC")
	
	if err := query.Find(&polls).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch polls"})
		return
	}
	
	c.JSON(http.StatusOK, polls)
}

// GetPoll returns a single poll by ID
func (pc *PollController) GetPoll(c *gin.Context) {
	id := c.Param("id")
	
	var poll models.Poll
	query := pc.DB.Preload("Options", func(db *gorm.DB) *gorm.DB {
		return db.Order("display_order ASC, id ASC")
	}).Preload("Options.Player").Preload("Category")
	
	if err := query.First(&poll, id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch poll"})
		return
	}
	
	// Check if user has voted
	hasVoted := false
	if userID, exists := c.Get("userID"); exists && userID != nil {
		var count int64
		pc.DB.Model(&models.PollVote{}).Where("poll_id = ? AND user_id = ?", poll.ID, userID).Count(&count)
		hasVoted = count > 0
	} else {
		// Check by IP hash or session token
		ipHash := pc.hashIP(c.ClientIP())
		sessionToken := c.GetHeader("X-Session-Token")
		if sessionToken == "" {
			sessionToken = c.Query("session_token")
		}
		
		var count int64
		query := pc.DB.Model(&models.PollVote{}).Where("poll_id = ?", poll.ID)
		if sessionToken != "" {
			query = query.Where("session_token = ?", sessionToken)
		} else {
			query = query.Where("ip_hash = ?", ipHash)
		}
		query.Count(&count)
		hasVoted = count > 0
	}
	
	// Add metadata
	response := gin.H{
		"poll":       poll,
		"has_voted":  hasVoted,
		"is_active":  poll.IsActive(),
		"can_show_results": poll.CanShowResults(hasVoted),
	}
	
	c.JSON(http.StatusOK, response)
}

// CreatePoll creates a new poll (admin only)
func (pc *PollController) CreatePoll(c *gin.Context) {
	var input struct {
		Title          string              `json:"title" binding:"required"`
		Description    string              `json:"description"`
		Type           string              `json:"type"`
		Status         string              `json:"status"`
		StartDate      *time.Time          `json:"start_date"`
		EndDate        *time.Time          `json:"end_date"`
		AllowMultiple  bool                `json:"allow_multiple"`
		MaxChoices     int                 `json:"max_choices"`
		ShowResults    string              `json:"show_results"`
		RequireAuth    bool                `json:"require_auth"`
		AllowGuestVote bool                `json:"allow_guest_vote"`
		Featured         bool                `json:"featured"`
		CategoryID       *uint               `json:"category_id"`
		RelatedMatchID   *uint               `json:"related_match_id"`
		RelatedArticleID *uint               `json:"related_article_id"`
		RelatedEventID   *uint               `json:"related_event_id"`
		RelatedVideoURL  string              `json:"related_video_url"`
		ImageURL         string              `json:"image_url"`
		Options        []struct {
			Text         string `json:"text" binding:"required"`
			Description  string `json:"description"`
			ImageURL     string `json:"image_url"`
			DisplayOrder int    `json:"display_order"`
			PlayerID     *uint  `json:"player_id"`
		} `json:"options" binding:"required,min=2"`
	}
	
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	
	// Get user ID
	userID, _ := c.Get("userID")
	
	// Create poll
	poll := models.Poll{
		Title:            input.Title,
		Description:      input.Description,
		Type:             input.Type,
		Status:           input.Status,
		StartDate:        input.StartDate,
		EndDate:          input.EndDate,
		AllowMultiple:    input.AllowMultiple,
		MaxChoices:       input.MaxChoices,
		ShowResults:      input.ShowResults,
		RequireAuth:      input.RequireAuth,
		AllowGuestVote:   input.AllowGuestVote,
		Featured:         input.Featured,
		CategoryID:       input.CategoryID,
		RelatedMatchID:   input.RelatedMatchID,
		RelatedArticleID: input.RelatedArticleID,
		RelatedEventID:   input.RelatedEventID,
		RelatedVideoURL:  input.RelatedVideoURL,
		ImageURL:         input.ImageURL,
		CreatedBy:        userID.(uint),
	}
	
	// Set defaults
	if poll.Type == "" {
		poll.Type = "single"
	}
	if poll.Status == "" {
		poll.Status = "draft"
	}
	if poll.ShowResults == "" {
		poll.ShowResults = "after_vote"
	}
	if poll.MaxChoices == 0 {
		poll.MaxChoices = 1
	}
	
	// Start transaction
	tx := pc.DB.Begin()
	
	if err := tx.Create(&poll).Error; err != nil {
		tx.Rollback()
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create poll"})
		return
	}
	
	// Create options
	for _, opt := range input.Options {
		option := models.PollOption{
			PollID:       poll.ID,
			Text:         opt.Text,
			Description:  opt.Description,
			ImageURL:     opt.ImageURL,
			DisplayOrder: opt.DisplayOrder,
			PlayerID:     opt.PlayerID,
		}
		if err := tx.Create(&option).Error; err != nil {
			tx.Rollback()
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create poll option"})
			return
		}
	}
	
	tx.Commit()
	
	// Reload with relations
	pc.DB.Preload("Options").Preload("Category").First(&poll, poll.ID)
	
	c.JSON(http.StatusCreated, poll)
}

// UpdatePoll updates an existing poll (admin only)
func (pc *PollController) UpdatePoll(c *gin.Context) {
	id := c.Param("id")
	
	var poll models.Poll
	if err := pc.DB.First(&poll, id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch poll"})
		return
	}
	
	var input struct {
		Title            *string     `json:"title"`
		Description      *string     `json:"description"`
		Type             *string     `json:"type"`
		Status           *string     `json:"status"`
		StartDate        *time.Time  `json:"start_date"`
		EndDate          *time.Time  `json:"end_date"`
		AllowMultiple    *bool       `json:"allow_multiple"`
		MaxChoices       *int        `json:"max_choices"`
		ShowResults      *string     `json:"show_results"`
		RequireAuth      *bool       `json:"require_auth"`
		AllowGuestVote   *bool       `json:"allow_guest_vote"`
		Featured         *bool       `json:"featured"`
		CategoryID       *uint       `json:"category_id"`
		RelatedMatchID   *uint       `json:"related_match_id"`
		RelatedArticleID *uint       `json:"related_article_id"`
		RelatedEventID   *uint       `json:"related_event_id"`
		RelatedVideoURL  *string     `json:"related_video_url"`
		ImageURL         *string     `json:"image_url"`
	}
	
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	
	// Update fields
	if input.Title != nil {
		poll.Title = *input.Title
	}
	if input.Description != nil {
		poll.Description = *input.Description
	}
	if input.Type != nil {
		poll.Type = *input.Type
	}
	if input.Status != nil {
		poll.Status = *input.Status
	}
	if input.StartDate != nil {
		poll.StartDate = input.StartDate
	}
	if input.EndDate != nil {
		poll.EndDate = input.EndDate
	}
	if input.AllowMultiple != nil {
		poll.AllowMultiple = *input.AllowMultiple
	}
	if input.MaxChoices != nil {
		poll.MaxChoices = *input.MaxChoices
	}
	if input.ShowResults != nil {
		poll.ShowResults = *input.ShowResults
	}
	if input.RequireAuth != nil {
		poll.RequireAuth = *input.RequireAuth
	}
	if input.AllowGuestVote != nil {
		poll.AllowGuestVote = *input.AllowGuestVote
	}
	if input.Featured != nil {
		poll.Featured = *input.Featured
	}
	if input.CategoryID != nil {
		poll.CategoryID = input.CategoryID
	}
	// For relationships, directly set the values (including nil to unlink)
	// GORM's Save will handle NULL values correctly
	poll.RelatedMatchID = input.RelatedMatchID
	poll.RelatedArticleID = input.RelatedArticleID
	poll.RelatedEventID = input.RelatedEventID
	if input.RelatedVideoURL != nil {
		poll.RelatedVideoURL = *input.RelatedVideoURL
	}
	if input.ImageURL != nil {
		poll.ImageURL = *input.ImageURL
	}
	
	if err := pc.DB.Save(&poll).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update poll"})
		return
	}
	
	// Reload with relations
	pc.DB.Preload("Options").Preload("Category").First(&poll, poll.ID)
	
	c.JSON(http.StatusOK, poll)
}

// DeletePoll deletes a poll (admin only)
func (pc *PollController) DeletePoll(c *gin.Context) {
	id := c.Param("id")
	
	var poll models.Poll
	if err := pc.DB.First(&poll, id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch poll"})
		return
	}
	
	// Soft delete
	if err := pc.DB.Delete(&poll).Error; err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete poll"})
		return
	}
	
	c.JSON(http.StatusOK, gin.H{"message": "Poll deleted successfully"})
}

// Vote handles vote submission
func (pc *PollController) Vote(c *gin.Context) {
	id := c.Param("id")
	
	var input struct {
		OptionIDs    []uint `json:"option_ids" binding:"required,min=1"`
		SessionToken string `json:"session_token"`
	}
	
	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid input: " + err.Error()})
		return
	}
	
	// Get poll
	var poll models.Poll
	if err := pc.DB.Preload("Options").First(&poll, id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch poll"})
		return
	}
	
	// Check if poll is active
	if !poll.IsActive() {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Poll is not currently accepting votes"})
		return
	}
	
	// Check authentication requirement
	userID, hasUser := c.Get("userID")
	if poll.RequireAuth && !hasUser {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Login required to vote"})
		return
	}
	
	if !poll.AllowGuestVote && !hasUser {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Guest voting not allowed"})
		return
	}
	
	// Check multiple choice limits
	if !poll.AllowMultiple && len(input.OptionIDs) > 1 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Only one choice allowed"})
		return
	}
	
	if poll.AllowMultiple && len(input.OptionIDs) > poll.MaxChoices {
		c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Maximum %d choices allowed", poll.MaxChoices)})
		return
	}
	
	// Check if already voted
	ipHash := pc.hashIP(c.ClientIP())
	sessionToken := input.SessionToken
	if sessionToken == "" {
		sessionToken = c.GetHeader("X-Session-Token")
	}
	
	var existingVoteCount int64
	query := pc.DB.Model(&models.PollVote{}).Where("poll_id = ?", poll.ID)
	
	if hasUser {
		query = query.Where("user_id = ?", userID)
	} else if sessionToken != "" {
		query = query.Where("session_token = ?", sessionToken)
	} else {
		query = query.Where("ip_hash = ?", ipHash)
	}
	
	query.Count(&existingVoteCount)
	
	if existingVoteCount > 0 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "You have already voted in this poll"})
		return
	}
	
	// Validate option IDs belong to this poll
	validOptions := make(map[uint]bool)
	for _, opt := range poll.Options {
		validOptions[opt.ID] = true
	}
	
	for _, optID := range input.OptionIDs {
		if !validOptions[optID] {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid option ID"})
			return
		}
	}
	
	// Start transaction
	tx := pc.DB.Begin()
	
	// Create votes
	userAgent := c.Request.UserAgent()
	for _, optionID := range input.OptionIDs {
		vote := models.PollVote{
			PollID:       poll.ID,
			OptionID:     optionID,
			IPHash:       ipHash,
			UserAgent:    userAgent,
			SessionToken: sessionToken,
		}
		
		if hasUser {
			uid := userID.(uint)
			vote.UserID = &uid
		}
		
		if err := tx.Create(&vote).Error; err != nil {
			tx.Rollback()
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to record vote"})
			return
		}
		
		// Increment option vote count
		if err := tx.Model(&models.PollOption{}).Where("id = ?", optionID).UpdateColumn("vote_count", gorm.Expr("vote_count + ?", 1)).Error; err != nil {
			tx.Rollback()
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update vote count"})
			return
		}
	}
	
	// Increment poll total votes
	if err := tx.Model(&models.Poll{}).Where("id = ?", poll.ID).UpdateColumn("total_votes", gorm.Expr("total_votes + ?", 1)).Error; err != nil {
		tx.Rollback()
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update total votes"})
		return
	}
	
	tx.Commit()
	
	// Reload poll with updated counts
	pc.DB.Preload("Options", func(db *gorm.DB) *gorm.DB {
		return db.Order("display_order ASC, id ASC")
	}).First(&poll, poll.ID)
	
	c.JSON(http.StatusOK, gin.H{
		"message": "Vote recorded successfully",
		"poll":    poll,
	})
}

// GetPollResults returns poll results
func (pc *PollController) GetPollResults(c *gin.Context) {
	id := c.Param("id")
	
	var poll models.Poll
	if err := pc.DB.Preload("Options", func(db *gorm.DB) *gorm.DB {
		return db.Order("display_order ASC, id ASC")
	}).First(&poll, id).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch poll"})
		return
	}
	
	// Check if user can see results
	hasVoted := false
	if userID, exists := c.Get("userID"); exists && userID != nil {
		var count int64
		pc.DB.Model(&models.PollVote{}).Where("poll_id = ? AND user_id = ?", poll.ID, userID).Count(&count)
		hasVoted = count > 0
	} else {
		ipHash := pc.hashIP(c.ClientIP())
		sessionToken := c.GetHeader("X-Session-Token")
		if sessionToken == "" {
			sessionToken = c.Query("session_token")
		}
		
		var count int64
		query := pc.DB.Model(&models.PollVote{}).Where("poll_id = ?", poll.ID)
		if sessionToken != "" {
			query = query.Where("session_token = ?", sessionToken)
		} else {
			query = query.Where("ip_hash = ?", ipHash)
		}
		query.Count(&count)
		hasVoted = count > 0
	}
	
	if !poll.CanShowResults(hasVoted) && !c.GetBool("isAdmin") {
		c.JSON(http.StatusForbidden, gin.H{"error": "Results are not available yet"})
		return
	}
	
	// Calculate percentages
	results := make([]gin.H, len(poll.Options))
	for i, option := range poll.Options {
		percentage := 0.0
		if poll.TotalVotes > 0 {
			percentage = float64(option.VoteCount) / float64(poll.TotalVotes) * 100
		}
		
		results[i] = gin.H{
			"option_id":   option.ID,
			"text":        option.Text,
			"vote_count":  option.VoteCount,
			"percentage":  percentage,
			"image_url":   option.ImageURL,
			"player_id":   option.PlayerID,
		}
	}
	
	c.JSON(http.StatusOK, gin.H{
		"poll_id":     poll.ID,
		"title":       poll.Title,
		"total_votes": poll.TotalVotes,
		"results":     results,
	})
}

// Helper function to hash IP addresses
func (pc *PollController) hashIP(ip string) string {
	hash := sha256.Sum256([]byte(ip + "poll-salt-2025"))
	return hex.EncodeToString(hash[:])
}

// GetPollStats returns statistics for admin (admin only)
func (pc *PollController) GetPollStats(c *gin.Context) {
	id := c.Param("id")
	pollID, err := strconv.Atoi(id)
	if err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid poll ID"})
		return
	}
	
	var poll models.Poll
	if err := pc.DB.Preload("Options").First(&poll, pollID).Error; err != nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Poll not found"})
		return
	}
	
	// Get vote distribution over time
	var votesByDay []struct {
		Date  string `json:"date"`
		Count int    `json:"count"`
	}
	
	pc.DB.Model(&models.PollVote{}).
		Select("DATE(created_at) as date, COUNT(*) as count").
		Where("poll_id = ?", pollID).
		Group("DATE(created_at)").
		Order("date ASC").
		Scan(&votesByDay)
	
	// Get authenticated vs guest votes
	var authVotes, guestVotes int64
	pc.DB.Model(&models.PollVote{}).Where("poll_id = ? AND user_id IS NOT NULL", pollID).Count(&authVotes)
	pc.DB.Model(&models.PollVote{}).Where("poll_id = ? AND user_id IS NULL", pollID).Count(&guestVotes)
	
	c.JSON(http.StatusOK, gin.H{
		"poll":               poll,
		"votes_by_day":       votesByDay,
		"authenticated_votes": authVotes,
		"guest_votes":        guestVotes,
	})
}
