package handlers

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"time"

	apperrors "veza-backend-api/internal/errors"
	"veza-backend-api/internal/models"
	"veza-backend-api/internal/services"

	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
)

// DashboardServiceInterface defines methods needed for dashboard handler
type DashboardServiceInterface interface {
	GetStats(ctx context.Context, startDate, endDate time.Time) ([]*services.AuditStats, error)
	GetUserActivity(ctx context.Context, userID uuid.UUID, limit int) ([]*services.AuditLog, error)
}

// TrackListParamsForDashboard represents parameters for listing tracks (to avoid import cycle)
type TrackListParamsForDashboard struct {
	Page      int
	Limit     int
	UserID    *uuid.UUID
	Genre     *string
	Format    *string
	SortBy    string
	SortOrder string
}

// TrackListFuncForDashboard is a function type for listing tracks (to avoid import cycle)
type TrackListFuncForDashboard func(ctx context.Context, params TrackListParamsForDashboard) ([]*models.Track, int64, error)

// TrackServiceInterfaceForDashboard defines methods needed for dashboard handler
type TrackServiceInterfaceForDashboard interface {
	ListTracks(ctx context.Context, params TrackListParamsForDashboard) ([]*models.Track, int64, error)
}

// DashboardHandler handles dashboard aggregation endpoint
// Action 2.1.1.2: Implements dashboard endpoint that aggregates multiple data sources
type DashboardHandler struct {
	auditService DashboardServiceInterface
	trackService TrackServiceInterfaceForDashboard
	logger       *zap.Logger
}

// NewDashboardHandler creates a new dashboard handler
// trackListFunc is a function that lists tracks (provided by router to avoid import cycle)
func NewDashboardHandler(
	auditService *services.AuditService,
	trackListFunc TrackListFuncForDashboard,
	logger *zap.Logger,
) *DashboardHandler {
	return &DashboardHandler{
		auditService: &dashboardAuditServiceWrapper{auditService: auditService},
		trackService: &dashboardTrackServiceWrapper{listTracksFunc: trackListFunc},
		logger:       logger,
	}
}

// NewDashboardHandlerWithInterface creates a new dashboard handler with interfaces (for testing)
func NewDashboardHandlerWithInterface(
	auditService DashboardServiceInterface,
	trackService TrackServiceInterfaceForDashboard,
	logger *zap.Logger,
) *DashboardHandler {
	return &DashboardHandler{
		auditService: auditService,
		trackService: trackService,
		logger:       logger,
	}
}

// dashboardAuditServiceWrapper wraps *services.AuditService to implement DashboardServiceInterface
type dashboardAuditServiceWrapper struct {
	auditService *services.AuditService
}

func (w *dashboardAuditServiceWrapper) GetStats(ctx context.Context, startDate, endDate time.Time) ([]*services.AuditStats, error) {
	return w.auditService.GetStats(ctx, startDate, endDate)
}

func (w *dashboardAuditServiceWrapper) GetUserActivity(ctx context.Context, userID uuid.UUID, limit int) ([]*services.AuditLog, error) {
	return w.auditService.GetUserActivity(ctx, userID, limit)
}

// dashboardTrackServiceWrapper wraps track service to implement TrackServiceInterfaceForDashboard
// This wrapper will be created in the router to avoid import cycles
type dashboardTrackServiceWrapper struct {
	listTracksFunc func(ctx context.Context, params TrackListParamsForDashboard) ([]*models.Track, int64, error)
}

func (w *dashboardTrackServiceWrapper) ListTracks(ctx context.Context, params TrackListParamsForDashboard) ([]*models.Track, int64, error) {
	return w.listTracksFunc(ctx, params)
}

// DashboardResponse represents the aggregated dashboard data
// Action 2.1.1.2: Matches DASHBOARD_ENDPOINT_CONTRACT.md
type DashboardResponse struct {
	Stats          DashboardStats   `json:"stats"`
	RecentActivity []RecentActivity `json:"recent_activity"`
	LibraryPreview LibraryPreview   `json:"library_preview"`
}

// DashboardStats represents dashboard statistics
type DashboardStats struct {
	TracksPlayed        int64   `json:"tracks_played"`
	MessagesSent        int64   `json:"messages_sent"`
	Favorites           int64   `json:"favorites"`
	ActiveFriends       int64   `json:"active_friends"`
	TracksPlayedChange  *string `json:"tracks_played_change,omitempty"`
	MessagesSentChange  *string `json:"messages_sent_change,omitempty"`
	FavoritesChange     *string `json:"favorites_change,omitempty"`
	ActiveFriendsChange *string `json:"active_friends_change,omitempty"`
	Period              string  `json:"period"`
}

// RecentActivity represents a recent activity item
type RecentActivity struct {
	ID          string                 `json:"id"`
	Type        string                 `json:"type"`
	Title       string                 `json:"title"`
	Description *string                `json:"description,omitempty"`
	Timestamp   string                 `json:"timestamp"`
	Icon        *string                `json:"icon,omitempty"`
	Metadata    map[string]interface{} `json:"metadata,omitempty"`
}

// LibraryPreview represents library preview data
type LibraryPreview struct {
	Items      []TrackPreview `json:"items"`
	TotalCount int64          `json:"total_count"`
	HasMore    bool           `json:"has_more"`
}

// TrackPreview represents a track in library preview
type TrackPreview struct {
	ID           string  `json:"id"`
	Title        string  `json:"title"`
	Artist       string  `json:"artist"`
	Duration     int     `json:"duration"`
	CoverArtPath *string `json:"cover_art_path,omitempty"`
	PlayCount    int64   `json:"play_count"`
	LikeCount    int64   `json:"like_count"`
	CreatedAt    string  `json:"created_at"`
}

// GetDashboard aggregates dashboard data from multiple sources
// @Summary      Get Dashboard Data
// @Description  Get aggregated dashboard data including stats, recent activity, and library preview
// @Tags         Dashboard
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        activity_limit  query     int     false  "Number of recent activity items (default: 10)"
// @Param        library_limit  query     int     false  "Number of library items (default: 5)"
// @Param        stats_period    query     string  false  "Time period for statistics: 7d, 30d, 90d, all (default: 30d)"
// @Success      200  {object}  handlers.APIResponse{data=DashboardResponse}
// @Failure      401  {object}  handlers.APIResponse "Unauthorized"
// @Failure      500  {object}  handlers.APIResponse "Internal server error"
// @Router       /api/v1/dashboard [get]
// Action 2.1.1.2: Implement dashboard aggregation endpoint
func (h *DashboardHandler) GetDashboard() gin.HandlerFunc {
	return func(c *gin.Context) {
		// Get user ID from context
		userIDInterface, exists := c.Get("user_id")
		if !exists {
			RespondWithError(c, int(apperrors.ErrCodeUnauthorized), "User not authenticated")
			return
		}

		userID, ok := userIDInterface.(uuid.UUID)
		if !ok {
			RespondWithError(c, int(apperrors.ErrCodeInternal), "Invalid user ID type")
			return
		}

		// Parse query parameters
		activityLimit := 10 // Default
		if limitStr := c.Query("activity_limit"); limitStr != "" {
			if parsed, err := strconv.Atoi(limitStr); err == nil && parsed > 0 && parsed <= 100 {
				activityLimit = parsed
			}
		}

		libraryLimit := 5 // Default
		if limitStr := c.Query("library_limit"); limitStr != "" {
			if parsed, err := strconv.Atoi(limitStr); err == nil && parsed > 0 && parsed <= 50 {
				libraryLimit = parsed
			}
		}

		statsPeriod := c.DefaultQuery("stats_period", "30d")
		if statsPeriod != "7d" && statsPeriod != "30d" && statsPeriod != "90d" && statsPeriod != "all" {
			statsPeriod = "30d" // Default to 30d if invalid
		}

		// Calculate date range for stats
		var startDate, endDate time.Time
		endDate = time.Now()
		switch statsPeriod {
		case "7d":
			startDate = endDate.AddDate(0, 0, -7)
		case "30d":
			startDate = endDate.AddDate(0, 0, -30)
		case "90d":
			startDate = endDate.AddDate(0, 0, -90)
		case "all":
			startDate = time.Time{} // Zero time for "all time"
		}

		ctx := c.Request.Context()

		// Fetch data in parallel (as per contract performance considerations)
		type statsResult struct {
			stats []*services.AuditStats
			err   error
		}
		type activityResult struct {
			activity []*services.AuditLog
			err      error
		}
		type tracksResult struct {
			tracks []*models.Track
			total  int64
			err    error
		}

		statsChan := make(chan statsResult, 1)
		activityChan := make(chan activityResult, 1)
		tracksChan := make(chan tracksResult, 1)

		// Fetch stats
		go func() {
			stats, err := h.auditService.GetStats(ctx, startDate, endDate)
			statsChan <- statsResult{stats: stats, err: err}
		}()

		// Fetch recent activity
		go func() {
			activity, err := h.auditService.GetUserActivity(ctx, userID, activityLimit)
			activityChan <- activityResult{activity: activity, err: err}
		}()

		// Fetch library preview
		go func() {
			tracks, total, err := h.trackService.ListTracks(ctx, TrackListParamsForDashboard{
				UserID:    &userID,
				Limit:     libraryLimit,
				Page:      1,
				SortBy:    "created_at",
				SortOrder: "desc",
			})
			tracksChan <- tracksResult{tracks: tracks, total: total, err: err}
		}()

		// Collect results
		statsRes := <-statsChan
		if statsRes.err != nil {
			h.logger.Error("Failed to get dashboard stats",
				zap.Error(statsRes.err),
				zap.String("user_id", userID.String()),
			)
			RespondWithError(c, int(apperrors.ErrCodeInternal), "Failed to fetch dashboard stats")
			return
		}

		activityRes := <-activityChan
		if activityRes.err != nil {
			h.logger.Error("Failed to get dashboard activity",
				zap.Error(activityRes.err),
				zap.String("user_id", userID.String()),
			)
			RespondWithError(c, int(apperrors.ErrCodeInternal), "Failed to fetch dashboard activity")
			return
		}

		tracksRes := <-tracksChan
		if tracksRes.err != nil {
			h.logger.Error("Failed to get dashboard library preview",
				zap.Error(tracksRes.err),
				zap.String("user_id", userID.String()),
			)
			RespondWithError(c, int(apperrors.ErrCodeInternal), "Failed to fetch library preview")
			return
		}

		// Aggregate stats from audit logs
		stats := h.aggregateStats(statsRes.stats, statsPeriod)

		// Convert activity logs to RecentActivity format
		recentActivity := h.convertActivityLogs(activityRes.activity)

		// Convert tracks to TrackPreview format
		libraryPreview := h.convertTracksToPreview(tracksRes.tracks, tracksRes.total, libraryLimit)

		// Build response
		response := DashboardResponse{
			Stats:          stats,
			RecentActivity: recentActivity,
			LibraryPreview: libraryPreview,
		}

		// Action 1.3.2.1: Use wrapped format helper
		RespondSuccess(c, http.StatusOK, response)
	}
}

// aggregateStats aggregates audit stats into dashboard stats format
func (h *DashboardHandler) aggregateStats(auditStats []*services.AuditStats, period string) DashboardStats {
	stats := DashboardStats{
		Period: period,
	}

	// Aggregate stats from audit logs
	for _, stat := range auditStats {
		switch stat.Action {
		case "play", "track_play":
			stats.TracksPlayed += stat.ActionCount
		case "message", "message_sent":
			stats.MessagesSent += stat.ActionCount
		case "favorite", "like", "track_like":
			stats.Favorites += stat.ActionCount
		}
		// Active friends: count unique users from interactions
		// This is a simplified calculation - could be enhanced
		if stat.UniqueUsers > stats.ActiveFriends {
			stats.ActiveFriends = stat.UniqueUsers
		}
	}

	// TODO: Calculate change percentages (compare current period to previous period)
	// This requires fetching stats for previous period, which is out of scope for initial implementation
	// Change percentages can be added in a follow-up task

	return stats
}

// convertActivityLogs converts audit logs to RecentActivity format
func (h *DashboardHandler) convertActivityLogs(logs []*services.AuditLog) []RecentActivity {
	activities := make([]RecentActivity, 0, len(logs))

	for _, log := range logs {
		activity := RecentActivity{
			ID:        log.ID.String(),
			Timestamp: log.Timestamp.Format(time.RFC3339),
			Metadata:  make(map[string]interface{}),
		}

		// Parse metadata from json.RawMessage
		var metadataMap map[string]interface{}
		if len(log.Metadata) > 0 {
			if err := json.Unmarshal(log.Metadata, &metadataMap); err == nil {
				activity.Metadata = metadataMap
			}
		}

		// Map action/resource to activity type and title
		switch log.Action {
		case "upload", "track_upload":
			activity.Type = "track_upload"
			activity.Title = "Nouvelle piste ajoutée"
			if metadataMap != nil {
				if fileName, ok := metadataMap["file_name"].(string); ok {
					desc := fileName
					activity.Description = &desc
				}
				if trackID, ok := metadataMap["track_id"].(string); ok {
					activity.Metadata["track_id"] = trackID
				}
			}
		case "message", "message_received":
			activity.Type = "message_received"
			activity.Title = "Message reçu"
			if metadataMap != nil {
				if fromUser, ok := metadataMap["from_user_id"].(string); ok {
					activity.Metadata["from_user_id"] = fromUser
				}
				if convID, ok := metadataMap["conversation_id"].(string); ok {
					activity.Metadata["conversation_id"] = convID
				}
			}
		case "favorite", "like", "track_like":
			activity.Type = "favorite_added"
			activity.Title = "Piste ajoutée aux favoris"
			if metadataMap != nil {
				if trackID, ok := metadataMap["track_id"].(string); ok {
					activity.Metadata["track_id"] = trackID
				}
			}
		case "playlist_create":
			activity.Type = "playlist_created"
			activity.Title = "Playlist créée"
			if metadataMap != nil {
				if playlistID, ok := metadataMap["playlist_id"].(string); ok {
					activity.Metadata["playlist_id"] = playlistID
				}
			}
		case "comment", "comment_add":
			activity.Type = "comment_added"
			activity.Title = "Commentaire ajouté"
			if metadataMap != nil {
				if trackID, ok := metadataMap["track_id"].(string); ok {
					activity.Metadata["track_id"] = trackID
				}
			}
		default:
			// Generic activity
			activity.Type = "other"
			activity.Title = fmt.Sprintf("%s: %s", log.Action, log.Resource)
		}

		activities = append(activities, activity)
	}

	return activities
}

// convertTracksToPreview converts tracks to TrackPreview format
func (h *DashboardHandler) convertTracksToPreview(tracks []*models.Track, total int64, limit int) LibraryPreview {
	previews := make([]TrackPreview, 0, len(tracks))

	for _, track := range tracks {
		preview := TrackPreview{
			ID:        track.ID.String(),
			Title:     track.Title,
			Artist:    track.Artist,
			Duration:  int(track.Duration),
			PlayCount: track.PlayCount,
			LikeCount: track.LikeCount,
			CreatedAt: track.CreatedAt.Format(time.RFC3339),
		}

		if track.CoverArtPath != "" {
			preview.CoverArtPath = &track.CoverArtPath
		}

		previews = append(previews, preview)
	}

	return LibraryPreview{
		Items:      previews,
		TotalCount: total,
		HasMore:    int64(len(previews)) < total,
	}
}
