package handlers

import (
	"context"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"gitlab.com/patrickbritton3/sojorn/go-backend/internal/models"
	"gitlab.com/patrickbritton3/sojorn/go-backend/internal/repository"
	"gitlab.com/patrickbritton3/sojorn/go-backend/internal/services"
	"gitlab.com/patrickbritton3/sojorn/go-backend/pkg/utils"
	"github.com/rs/zerolog/log"
)

type UserHandler struct {
	repo                *repository.UserRepository
	postRepo            *repository.PostRepository
	notificationService *services.NotificationService
	assetService        *services.AssetService
}

func NewUserHandler(repo *repository.UserRepository, postRepo *repository.PostRepository, notificationService *services.NotificationService, assetService *services.AssetService) *UserHandler {
	return &UserHandler{
		repo:                repo,
		postRepo:            postRepo,
		notificationService: notificationService,
		assetService:        assetService,
	}
}

func (h *UserHandler) GetProfile(c *gin.Context) {
	userID := c.Param("id")
	handle, handleExists := c.GetQuery("handle")

	var profile *models.Profile
	var err error

	if userID != "" {
		profile, err = h.repo.GetProfileByID(c.Request.Context(), userID)
	} else if handleExists {
		if handle == "" {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Handle cannot be empty"})
			return
		}
		profile, err = h.repo.GetProfileByHandle(c.Request.Context(), handle)
	} else {
		// Fallback to current authenticated user
		if val, exists := c.Get("user_id"); exists {
			userID = val.(string)
			profile, err = h.repo.GetProfileByID(c.Request.Context(), userID)
		}
	}

	if err != nil || profile == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "Profile not found"})
		return
	}

	// Use the profile ID for subsequent lookups
	actualUserID := profile.ID.String()

	// Get stats
	stats, _ := h.repo.GetProfileStats(c.Request.Context(), actualUserID)

	// Check follow status if authenticated
	isFollowing := false
	isFollowedBy := false
	followStatus := ""
	if currentUserID, exists := c.Get("user_id"); exists && currentUserID.(string) != actualUserID {
		var err error
		isFollowing, err = h.repo.IsFollowing(c.Request.Context(), currentUserID.(string), actualUserID)
		if err != nil {
			log.Error().Err(err).Msg("Failed to check isFollowing")
		}
		isFollowedBy, err = h.repo.IsFollowing(c.Request.Context(), actualUserID, currentUserID.(string))
		if err != nil {
			log.Error().Err(err).Msg("Failed to check isFollowedBy")
		}
		followStatus, _ = h.repo.GetFollowStatus(c.Request.Context(), currentUserID.(string), actualUserID)
	}

	// Sign URLs
	if profile.AvatarURL != nil {
		signed := h.assetService.SignImageURL(*profile.AvatarURL)
		profile.AvatarURL = &signed
	}
	if profile.CoverURL != nil {
		signed := h.assetService.SignImageURL(*profile.CoverURL)
		profile.CoverURL = &signed
	}

	c.JSON(http.StatusOK, gin.H{
		"profile":        profile,
		"stats":          stats,
		"is_following":   isFollowing,
		"is_followed_by": isFollowedBy,
		"is_friend":      isFollowing && isFollowedBy,
		"follow_status":  followStatus,
		"is_private":     profile.IsPrivate,
	})
}

func (h *UserHandler) Follow(c *gin.Context) {
	followerID, exists := c.Get("user_id")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
		return
	}

	followingID := c.Param("id")
	if followingID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "User ID required"})
		return
	}

	status, err := h.repo.FollowUser(c.Request.Context(), followerID.(string), followingID)
	if err != nil {
		if strings.Contains(err.Error(), "cannot follow self") {
			c.JSON(http.StatusBadRequest, gin.H{"error": "Cannot follow self"})
			return
		}
		if strings.Contains(err.Error(), "23503") || strings.Contains(err.Error(), "target profile not found") { // FK Violation or custom error
			c.JSON(http.StatusNotFound, gin.H{"error": "User to follow not found"})
			return
		}
		log.Error().Err(err).Msg("Failed to follow user")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to follow user", "details": err.Error()})
		return
	}

	// Send Notification
	if h.notificationService != nil {
		go func(targetID string, actorID string, isPending bool) {
			_ = h.notificationService.NotifyFollow(context.Background(), targetID, actorID, isPending)
		}(followingID, followerID.(string), status == "pending")
	}

	c.JSON(http.StatusOK, gin.H{"message": "Follow update successful", "status": status})
}

func (h *UserHandler) Unfollow(c *gin.Context) {
	followerID, exists := c.Get("user_id")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
		return
	}

	followingID := c.Param("id")
	if followingID == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "User ID required"})
		return
	}

	if err := h.repo.UnfollowUser(c.Request.Context(), followerID.(string), followingID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to unfollow user"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Unfollowed successfully"})
}
func (h *UserHandler) UpdateProfile(c *gin.Context) {
	userIDStr, _ := c.Get("user_id")
	userID, _ := uuid.Parse(userIDStr.(string))

	var req struct {
		Handle              *string  `json:"handle"`
		DisplayName         *string  `json:"display_name"`
		Bio                 *string  `json:"bio"`
		AvatarURL           *string  `json:"avatar_url"`
		CoverURL            *string  `json:"cover_url"`
		Location            *string  `json:"location"`
		Website             *string  `json:"website"`
		Interests           []string `json:"interests"`
		IdentityKey         *string  `json:"identity_key"`
		RegistrationID      *int     `json:"registration_id"`
		EncryptedPrivateKey *string  `json:"encrypted_private_key"`
		IsPrivate           *bool    `json:"is_private"`
		IsOfficial          *bool    `json:"is_official"`
	}

	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	// Validate handle if being changed
	if req.Handle != nil {
		handleCheck := services.ValidateUsernameWithDB(c.Request.Context(), h.repo.Pool(), *req.Handle)
		if handleCheck.Violation != services.UsernameOK {
			status := http.StatusBadRequest
			if handleCheck.Violation == services.UsernameReserved {
				status = http.StatusForbidden
			}
			c.JSON(status, gin.H{"error": handleCheck.Message})
			return
		}
	}

	// Validate display name if being changed
	if req.DisplayName != nil {
		nameCheck := services.ValidateDisplayName(*req.DisplayName)
		if nameCheck.Violation != services.UsernameOK {
			c.JSON(http.StatusBadRequest, gin.H{"error": nameCheck.Message})
			return
		}
	}

	profile := &models.Profile{
		ID:                  userID,
		Handle:              req.Handle,
		DisplayName:         req.DisplayName,
		Bio:                 req.Bio,
		AvatarURL:           req.AvatarURL,
		CoverURL:            req.CoverURL,
		Location:            req.Location,
		Website:             req.Website,
		Interests:           req.Interests,
		IdentityKey:         req.IdentityKey,
		RegistrationID:      req.RegistrationID,
		EncryptedPrivateKey: req.EncryptedPrivateKey,
		IsPrivate:           req.IsPrivate,
		IsOfficial:          req.IsOfficial,
	}

	err := h.repo.UpdateProfile(c.Request.Context(), profile)
	if err != nil {
		// Log error
		log.Error().Err(err).Msg("Failed to update profile")

		// Check for duplicate handle
		if strings.Contains(err.Error(), "23505") {
			c.JSON(http.StatusConflict, gin.H{"error": "Handle already taken"})
			return
		}
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update profile", "details": err.Error()})
		return
	}

	updated, _ := h.repo.GetProfileByID(c.Request.Context(), userID.String())
	c.JSON(http.StatusOK, gin.H{"profile": updated})
}

func (h *UserHandler) GetSavedPosts(c *gin.Context) {
	currentUserID := c.GetString("user_id") // Authenticated user
	targetID := c.Param("id")

	if targetID == "" || targetID == "me" {
		targetID = currentUserID
	}

	// TODO: Add privacy check here if viewing another user's saved posts

	limit := utils.GetQueryInt(c, "limit", 20)
	offset := utils.GetQueryInt(c, "offset", 0)

	// Check viewer's NSFW preference
	showNSFW := false
	if settings, err := h.repo.GetUserSettings(c.Request.Context(), currentUserID); err == nil && settings.NSFWEnabled != nil {
		showNSFW = *settings.NSFWEnabled
	}

	posts, err := h.postRepo.GetSavedPosts(c.Request.Context(), targetID, limit, offset, showNSFW)
	if err != nil {
		log.Error().Err(err).Msg("Failed to fetch saved posts")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch saved posts"})
		return
	}
	// Sign URLs
	for i := range posts {
		if posts[i].ImageURL != nil {
			signed := h.assetService.SignImageURL(*posts[i].ImageURL)
			posts[i].ImageURL = &signed
		}
		if posts[i].VideoURL != nil {
			signed := h.assetService.SignVideoURL(*posts[i].VideoURL)
			posts[i].VideoURL = &signed
		}
		if posts[i].ThumbnailURL != nil {
			signed := h.assetService.SignImageURL(*posts[i].ThumbnailURL)
			posts[i].ThumbnailURL = &signed
		}
	}
	c.JSON(http.StatusOK, gin.H{"posts": posts})
}

func (h *UserHandler) GetLikedPosts(c *gin.Context) {
	userID := c.GetString("user_id")
	limit := 20
	offset := 0

	// Check viewer's NSFW preference
	showNSFW := false
	if settings, err := h.repo.GetUserSettings(c.Request.Context(), userID); err == nil && settings.NSFWEnabled != nil {
		showNSFW = *settings.NSFWEnabled
	}

	posts, err := h.postRepo.GetLikedPosts(c.Request.Context(), userID, limit, offset, showNSFW)
	if err != nil {
		log.Error().Err(err).Msg("Failed to fetch liked posts")
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch liked posts"})
		return
	}
	// Sign URLs
	for i := range posts {
		if posts[i].ImageURL != nil {
			signed := h.assetService.SignImageURL(*posts[i].ImageURL)
			posts[i].ImageURL = &signed
		}
		if posts[i].VideoURL != nil {
			signed := h.assetService.SignVideoURL(*posts[i].VideoURL)
			posts[i].VideoURL = &signed
		}
		if posts[i].ThumbnailURL != nil {
			signed := h.assetService.SignImageURL(*posts[i].ThumbnailURL)
			posts[i].ThumbnailURL = &signed
		}
	}
	c.JSON(http.StatusOK, gin.H{"posts": posts})
}

func (h *UserHandler) AcceptFollowRequest(c *gin.Context) {
	userIdStr, _ := c.Get("user_id")
	requesterId := c.Param("id")

	if err := h.repo.AcceptFollowRequest(c.Request.Context(), userIdStr.(string), requesterId); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to accept follow request"})
		return
	}

	// Harmony & Notifications
	if h.notificationService != nil {
		go func(targetID, actorID string) {
			// 1. Update Harmony Scores (Mutual gain)
			_ = h.repo.UpdateHarmonyScore(context.Background(), targetID, 2)
			_ = h.repo.UpdateHarmonyScore(context.Background(), actorID, 2)

			// 2. Send Notification to requester
			_ = h.notificationService.NotifyFollowAccepted(context.Background(), actorID, targetID)
		}(userIdStr.(string), requesterId)
	}

	c.JSON(http.StatusOK, gin.H{"message": "Follow request accepted"})
}

func (h *UserHandler) GetPendingFollowRequests(c *gin.Context) {
	userIdStr, _ := c.Get("user_id")

	requests, err := h.repo.GetPendingFollowRequests(c.Request.Context(), userIdStr.(string))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch pending follow requests"})
		return
	}

	// Sign URLs for avatars in requests
	for i := range requests {
		if avatar, ok := requests[i]["avatar_url"].(string); ok && avatar != "" {
			requests[i]["avatar_url"] = h.assetService.SignImageURL(avatar)
		}
	}

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

func (h *UserHandler) RejectFollowRequest(c *gin.Context) {
	userIdStr, _ := c.Get("user_id")
	requesterId := c.Param("id")

	if err := h.repo.RejectFollowRequest(c.Request.Context(), userIdStr.(string), requesterId); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reject follow request"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Follow request rejected"})
}

func (h *UserHandler) BlockUser(c *gin.Context) {
	blockerID, _ := c.Get("user_id")
	blockedID := c.Param("id")
	actorIP := c.ClientIP()

	if err := h.repo.BlockUser(c.Request.Context(), blockerID.(string), blockedID, actorIP); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to block user"})
		return
	}

	// Also unfollow automatically
	_ = h.repo.UnfollowUser(c.Request.Context(), blockerID.(string), blockedID)
	_ = h.repo.UnfollowUser(c.Request.Context(), blockedID, blockerID.(string))

	c.JSON(http.StatusOK, gin.H{"message": "User blocked"})
}

func (h *UserHandler) ReportUser(c *gin.Context) {
	reporterID, _ := c.Get("user_id")

	var input struct {
		TargetUserID  string `json:"target_user_id" binding:"required"`
		PostID        string `json:"post_id"`
		CommentID     string `json:"comment_id"`
		ViolationType string `json:"violation_type" binding:"required"`
		Description   string `json:"description"`
	}

	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	rID, _ := uuid.Parse(reporterID.(string))
	tID, _ := uuid.Parse(input.TargetUserID)

	report := &models.Report{
		ReporterID:    rID,
		TargetUserID:  tID,
		ViolationType: input.ViolationType,
		Description:   input.Description,
	}

	if input.PostID != "" {
		pID, _ := uuid.Parse(input.PostID)
		report.PostID = &pID
	}
	if input.CommentID != "" {
		cID, _ := uuid.Parse(input.CommentID)
		report.CommentID = &cID
	}

	if err := h.repo.CreateReport(c.Request.Context(), report); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create report"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "Report submitted successfully"})
}

func (h *UserHandler) UnblockUser(c *gin.Context) {
	blockerID, _ := c.Get("user_id")
	blockedID := c.Param("id")

	if err := h.repo.UnblockUser(c.Request.Context(), blockerID.(string), blockedID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to unblock user"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "User unblocked"})
}

func (h *UserHandler) GetBlockedUsers(c *gin.Context) {
	userID, _ := c.Get("user_id")

	blocked, err := h.repo.GetBlockedUsers(c.Request.Context(), userID.(string))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch blocked users"})
		return
	}

	// Sign URLs
	for i := range blocked {
		if blocked[i].AvatarURL != nil {
			signed := h.assetService.SignImageURL(*blocked[i].AvatarURL)
			blocked[i].AvatarURL = &signed
		}
	}

	c.JSON(http.StatusOK, gin.H{"users": blocked})
}

func (h *UserHandler) BlockUserByHandle(c *gin.Context) {
	actorID, _ := c.Get("user_id")
	actorIP := c.ClientIP()

	var input struct {
		Handle string `json:"handle" binding:"required"`
	}

	if err := c.ShouldBindJSON(&input); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "Handle is required"})
		return
	}

	if err := h.repo.BlockUserByHandle(c.Request.Context(), actorID.(string), input.Handle, actorIP); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to block user"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "User blocked by handle"})
}

func (h *UserHandler) GetTrustState(c *gin.Context) {
	userIDStr, exists := c.Get("user_id")
	if !exists {
		c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
		return
	}

	state, err := h.repo.GetTrustState(c.Request.Context(), userIDStr.(string))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch trust state"})
		return
	}

	c.JSON(http.StatusOK, state)
}

// ========================================================================
// Social Graph: Followers & Following
// ========================================================================

// GetFollowers returns the list of users following the specified user
func (h *UserHandler) GetFollowers(c *gin.Context) {
	targetUserID := c.Param("id")
	limit := utils.GetQueryInt(c, "limit", 20)
	offset := utils.GetQueryInt(c, "offset", 0)

	followers, err := h.repo.GetFollowers(c.Request.Context(), targetUserID, limit, offset)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch followers"})
		return
	}

	// Sign avatar URLs
	for i := range followers {
		if followers[i].AvatarURL != nil {
			signed := h.assetService.SignImageURL(*followers[i].AvatarURL)
			followers[i].AvatarURL = &signed
		}
	}

	c.JSON(http.StatusOK, gin.H{"followers": followers, "count": len(followers)})
}

// GetFollowing returns the list of users the specified user is following
func (h *UserHandler) GetFollowing(c *gin.Context) {
	targetUserID := c.Param("id")
	limit := utils.GetQueryInt(c, "limit", 20)
	offset := utils.GetQueryInt(c, "offset", 0)

	following, err := h.repo.GetFollowing(c.Request.Context(), targetUserID, limit, offset)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch following"})
		return
	}

	// Sign avatar URLs
	for i := range following {
		if following[i].AvatarURL != nil {
			signed := h.assetService.SignImageURL(*following[i].AvatarURL)
			following[i].AvatarURL = &signed
		}
	}

	c.JSON(http.StatusOK, gin.H{"following": following, "count": len(following)})
}

// ========================================================================
// Circle (Close Friends) Management
// ========================================================================

// AddToCircle adds a user to the current user's circle
func (h *UserHandler) AddToCircle(c *gin.Context) {
	userID, _ := c.Get("user_id")
	memberID := c.Param("id")

	if err := h.repo.AddToCircle(c.Request.Context(), userID.(string), memberID); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "User added to circle"})
}

// RemoveFromCircle removes a user from the current user's circle
func (h *UserHandler) RemoveFromCircle(c *gin.Context) {
	userID, _ := c.Get("user_id")
	memberID := c.Param("id")

	if err := h.repo.RemoveFromCircle(c.Request.Context(), userID.(string), memberID); err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to remove from circle"})
		return
	}

	c.JSON(http.StatusOK, gin.H{"message": "User removed from circle"})
}

// GetCircleMembers returns all members of the current user's circle
func (h *UserHandler) GetCircleMembers(c *gin.Context) {
	userID, _ := c.Get("user_id")

	members, err := h.repo.GetCircleMembers(c.Request.Context(), userID.(string))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch circle members"})
		return
	}

	// Sign avatar URLs
	for i := range members {
		if members[i].AvatarURL != nil {
			signed := h.assetService.SignImageURL(*members[i].AvatarURL)
			members[i].AvatarURL = &signed
		}
	}

	c.JSON(http.StatusOK, gin.H{"members": members, "count": len(members)})
}

// ========================================================================
// Data Export (Portability)
// ========================================================================

// ========================================================================
// Block list bulk import
// ========================================================================

// BulkBlockUsers POST /users/me/blocks/bulk
// Body: {"handles": ["alice", "bob", ...]}
// Blocks each handle, auto-unfollows both ways.
func (h *UserHandler) BulkBlockUsers(c *gin.Context) {
	actorID, _ := c.Get("user_id")
	actorIP := c.ClientIP()

	var req struct {
		Handles []string `json:"handles" binding:"required"`
	}
	if err := c.ShouldBindJSON(&req); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}
	if len(req.Handles) > 500 {
		c.JSON(http.StatusBadRequest, gin.H{"error": "maximum 500 handles per request"})
		return
	}

	var blocked int
	var notFound []string
	var alreadyBlocked []string

	for _, handle := range req.Handles {
		handle = strings.TrimSpace(strings.TrimPrefix(handle, "@"))
		if handle == "" {
			continue
		}
		err := h.repo.BlockUserByHandle(c.Request.Context(), actorID.(string), handle, actorIP)
		if err != nil {
			msg := err.Error()
			if strings.Contains(msg, "not found") {
				notFound = append(notFound, handle)
			} else if strings.Contains(msg, "conflict") || strings.Contains(msg, "duplicate") {
				alreadyBlocked = append(alreadyBlocked, handle)
			} else {
				log.Warn().Err(err).Str("handle", handle).Msg("bulk block: unexpected error")
			}
			continue
		}
		blocked++
	}

	c.JSON(http.StatusOK, gin.H{
		"blocked":        blocked,
		"not_found":      notFound,
		"already_blocked": alreadyBlocked,
	})
}

// GetUserByHandle resolves a public profile by @handle.
// Used by the capsule invite flow so the client can look up a user's public key before encrypting.
func (h *UserHandler) GetUserByHandle(c *gin.Context) {
	handle := strings.TrimPrefix(strings.TrimSpace(c.Param("handle")), "@")
	if handle == "" {
		c.JSON(http.StatusBadRequest, gin.H{"error": "handle is required"})
		return
	}
	profile, err := h.repo.GetProfileByHandle(c.Request.Context(), handle)
	if err != nil || profile == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "user not found"})
		return
	}
	c.JSON(http.StatusOK, profile)
}

// ExportData streams user data as JSON for portability/GDPR compliance
func (h *UserHandler) ExportData(c *gin.Context) {
	userID, _ := c.Get("user_id")

	exportData, err := h.repo.ExportUserData(c.Request.Context(), userID.(string))
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate export"})
		return
	}

	// Sign all image/video URLs in posts
	for i := range exportData.Posts {
		if exportData.Posts[i].ImageURL != nil {
			signed := h.assetService.SignImageURL(*exportData.Posts[i].ImageURL)
			exportData.Posts[i].ImageURL = &signed
		}
		if exportData.Posts[i].VideoURL != nil {
			signed := h.assetService.SignVideoURL(*exportData.Posts[i].VideoURL)
			exportData.Posts[i].VideoURL = &signed
		}
	}

	// Set headers for file download
	c.Header("Content-Disposition", "attachment; filename=sojorn_data_export.json")
	c.Header("Content-Type", "application/json")
	c.JSON(http.StatusOK, exportData)
}
