package handlers

import (
	"bookhoard/internal/database"
	"context"
	"errors"
	"fmt"
	"net/http"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/labstack/echo/v5"
)

type AnalyticsHandler struct {
	db *database.Queries
}

func NewAnalyticsHandler(db *database.Queries) *AnalyticsHandler {
	return &AnalyticsHandler{
		db: db,
	}
}

type ReadingStatsResponse struct {
	TotalBooksRead      int            `json:"total_books_read"`
	TotalPagesRead      int            `json:"total_pages_read"`
	TotalReadingTime    int            `json:"total_reading_time_minutes"`
	AverageSessionTime  float64        `json:"average_session_time_minutes"`
	LongestSession      int            `json:"longest_session_minutes"`
	MostActiveDay       string         `json:"most_active_day_of_week"`
	CompletionRate      float64        `json:"completion_rate"`
	DailyReadingMinutes []DailyReading `json:"daily_reading_minutes"`
}

type DailyReading struct {
	Date    string `json:"date"`
	Minutes int    `json:"minutes"`
	Pages   int    `json:"pages"`
}

type DeviceUsageResponse struct {
	Devices []DeviceUsage `json:"devices"`
}

type DeviceUsage struct {
	DeviceID         string  `json:"device_id"`
	DeviceName       string  `json:"device_name"`
	DeviceType       string  `json:"device_type"`
	SyncCount        int     `json:"sync_count"`
	LastSync         string  `json:"last_sync"`
	TotalTimeSeconds int     `json:"total_time_seconds"`
	TotalTimeMinutes float64 `json:"total_time_minutes"`
}

type PopularBooksResponse struct {
	Books []PopularBook `json:"books"`
}

type PopularBook struct {
	MediaItemID   string  `json:"media_item_id"`
	Title         string  `json:"title"`
	Author        string  `json:"author"`
	ReadCount     int     `json:"read_count"`
	AvgCompletion float64 `json:"avg_completion"`
	LastRead      string  `json:"last_read"`
}

func (h *AnalyticsHandler) GetReadingStats(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	startDate := c.QueryParam("start_date")
	endDate := c.QueryParam("end_date")

	if startDate == "" {
		startDate = time.Now().AddDate(0, -1, 0).Format("01-02-2006")
	}
	if endDate == "" {
		endDate = time.Now().Format("01-02-2006")
	}

	startTime, err := time.Parse("01-02-2006", startDate)
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "invalid start_date format")
	}

	endTime, err := time.Parse("01-02-2006", endDate)
	if err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, "invalid end_date format")
	}

	ctx := context.Background()

	history, err := h.db.GetUserReadingHistory(ctx, database.GetUserReadingHistoryParams{
		UserID:      user.ID,
		CreatedAt:   pgtype.Timestamptz{Time: startTime, Valid: true},
		CreatedAt_2: pgtype.Timestamptz{Time: endTime, Valid: true},
	})

	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to get reading history")
	}

	stats := h.calculateReadingStats(history)

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

func (h *AnalyticsHandler) calculateReadingStats(history []database.GetUserReadingHistoryRow) ReadingStatsResponse {
	stats := ReadingStatsResponse{
		DailyReadingMinutes: make([]DailyReading, 0),
	}

	totalTime := 0
	totalPages := 0
	longestSession := 0
	sessionTimes := make([]int, 0)

	dailyMap := make(map[string]*DailyReading)

	for _, entry := range history {
		if entry.TimeSpentSeconds.Valid {
			minutes := entry.TimeSpentSeconds.Int32 / 60
			totalTime += int(minutes)
			sessionTimes = append(sessionTimes, int(minutes))

			if int(minutes) > longestSession {
				longestSession = int(minutes)
			}

			dateKey := entry.CreatedAt.Time.Format("01-02-2006")
			if dailyMap[dateKey] == nil {
				dailyMap[dateKey] = &DailyReading{
					Date: dateKey,
				}
			}
			dailyMap[dateKey].Minutes += int(minutes)
		}

		if entry.PagesRead.Valid {
			totalPages += int(entry.PagesRead.Int32)
			dateKey := entry.CreatedAt.Time.Format("01-02-2006")
			if dailyMap[dateKey] != nil {
				dailyMap[dateKey].Pages += int(entry.PagesRead.Int32)
			}
		}
	}

	stats.TotalReadingTime = totalTime
	stats.TotalPagesRead = totalPages
	stats.LongestSession = longestSession

	if len(sessionTimes) > 0 {
		sum := 0
		for _, t := range sessionTimes {
			sum += t
		}
		stats.AverageSessionTime = float64(sum) / float64(len(sessionTimes))
	}

	booksCompleted := 0
	totalBooks := len(history)
	if totalBooks > 0 {
		for _, entry := range history {
			if entry.ProgressPercentage.Valid && entry.ProgressPercentage.Float64 >= 100.0 {
				booksCompleted++
			}
		}
	}
	stats.TotalBooksRead = booksCompleted
	if totalBooks > 0 {
		stats.CompletionRate = float64(booksCompleted) / float64(totalBooks)
	}

	stats.MostActiveDay = h.calculateMostActiveDay(history)

	for _, data := range dailyMap {
		stats.DailyReadingMinutes = append(stats.DailyReadingMinutes, *data)
	}

	return stats
}

func (h *AnalyticsHandler) calculateMostActiveDay(history []database.GetUserReadingHistoryRow) string {
	dayCount := make(map[string]int)

	days := []string{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}

	for _, entry := range history {
		day := days[entry.CreatedAt.Time.Weekday()]
		dayCount[day]++
	}

	mostActive := "Monday"
	maxCount := 0

	for day, count := range dayCount {
		if count > maxCount {
			maxCount = count
			mostActive = day
		}
	}

	return mostActive
}

func (h *AnalyticsHandler) GetDeviceUsage(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	ctx := context.Background()

	usage, err := h.db.GetUserDeviceUsage(ctx, user.ID)
	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to get device usage")
	}

	devices := make([]DeviceUsage, 0, len(usage))

	for _, u := range usage {
		lastSync := ""
		if u.LastSync != nil {
			if t, ok := u.LastSync.(time.Time); ok {
				lastSync = t.Format("01-02-2006 03:04:05 PM")
			}
		}

		totalTimeMinutes := float64(u.TotalTimeSeconds) / 60.0

		devices = append(devices, DeviceUsage{
			DeviceID:         formatUUID(u.ID),
			DeviceName:       u.DeviceName,
			DeviceType:       u.DeviceType,
			SyncCount:        int(u.SyncCount),
			LastSync:         lastSync,
			TotalTimeSeconds: int(u.TotalTimeSeconds),
			TotalTimeMinutes: totalTimeMinutes,
		})
	}

	return c.JSON(http.StatusOK, DeviceUsageResponse{
		Devices: devices,
	})
}

func (h *AnalyticsHandler) GetPopularBooks(c *echo.Context) error {
	user := c.Get("user").(database.Users)

	limit := c.QueryParam("limit")
	if limit == "" {
		limit = "10"
	}

	limitInt := int32(10)
	if parsedLimit, err := parseLimit(limit); err == nil {
		limitInt = parsedLimit
	}

	ctx := context.Background()

	books, err := h.db.GetPopularBooks(ctx, database.GetPopularBooksParams{
		UserID: user.ID,
		Limit:  limitInt,
	})

	if err != nil && !errors.Is(err, pgx.ErrNoRows) {
		return echo.NewHTTPError(http.StatusInternalServerError, "failed to get popular books")
	}

	popularBooks := make([]PopularBook, 0, len(books))

	for _, book := range books {
		lastRead := ""
		if book.LastRead != nil {
			if t, ok := book.LastRead.(time.Time); ok {
				lastRead = t.Format("01-02-2006 03:04:05 PM")
			}
		}

		popularBooks = append(popularBooks, PopularBook{
			MediaItemID:   formatUUID(book.ID),
			Title:         book.Title,
			Author:        book.Author.String,
			ReadCount:     int(book.ReadCount),
			AvgCompletion: book.AvgCompletion,
			LastRead:      lastRead,
		})
	}

	return c.JSON(http.StatusOK, PopularBooksResponse{
		Books: popularBooks,
	})
}

func parseLimit(limitStr string) (int32, error) {
	var limit int32
	if _, err := fmt.Sscanf(limitStr, "%d", &limit); err != nil {
		return 0, err
	}
	return limit, nil
}

func formatUUID(id pgtype.UUID) string {
	if !id.Valid {
		return ""
	}
	u := uuid.UUID(id.Bytes)
	return u.String()
}
