package repositories

import (
	"time"

	"gorm.io/gorm"

	"github.com/treytartt/honeydue-api/internal/models"
)

// ReminderRepository handles database operations for task reminder logs
type ReminderRepository struct {
	db *gorm.DB
}

// NewReminderRepository creates a new reminder repository
func NewReminderRepository(db *gorm.DB) *ReminderRepository {
	return &ReminderRepository{db: db}
}

// HasSentReminder checks if a reminder has already been sent for the given
// task, user, due date, and reminder stage.
func (r *ReminderRepository) HasSentReminder(taskID, userID uint, dueDate time.Time, stage models.ReminderStage) (bool, error) {
	// Normalize to date only
	dueDateOnly := time.Date(dueDate.Year(), dueDate.Month(), dueDate.Day(), 0, 0, 0, 0, time.UTC)

	var count int64
	err := r.db.Model(&models.TaskReminderLog{}).
		Where("task_id = ? AND user_id = ? AND due_date = ? AND reminder_stage = ?",
			taskID, userID, dueDateOnly, stage).
		Count(&count).Error

	if err != nil {
		return false, err
	}

	return count > 0, nil
}

// ReminderKey uniquely identifies a reminder that may have been sent.
type ReminderKey struct {
	TaskID  uint
	UserID  uint
	DueDate time.Time
	Stage   models.ReminderStage
}

// HasSentReminderBatch checks which reminders from the given list have already been sent.
// Returns a set of indices into the input slice that have already been sent.
// This replaces N individual HasSentReminder calls with a single query.
func (r *ReminderRepository) HasSentReminderBatch(keys []ReminderKey) (map[int]bool, error) {
	result := make(map[int]bool)
	if len(keys) == 0 {
		return result, nil
	}

	// Build a lookup from (task_id, user_id, due_date, stage) -> index
	type normalizedKey struct {
		TaskID  uint
		UserID  uint
		DueDate string
		Stage   models.ReminderStage
	}
	keyToIdx := make(map[normalizedKey][]int, len(keys))

	// Collect unique task IDs and user IDs for the WHERE clause
	taskIDSet := make(map[uint]bool)
	userIDSet := make(map[uint]bool)
	for i, k := range keys {
		taskIDSet[k.TaskID] = true
		userIDSet[k.UserID] = true
		dueDateOnly := time.Date(k.DueDate.Year(), k.DueDate.Month(), k.DueDate.Day(), 0, 0, 0, 0, time.UTC)
		nk := normalizedKey{
			TaskID:  k.TaskID,
			UserID:  k.UserID,
			DueDate: dueDateOnly.Format("2006-01-02"),
			Stage:   k.Stage,
		}
		keyToIdx[nk] = append(keyToIdx[nk], i)
	}

	taskIDs := make([]uint, 0, len(taskIDSet))
	for id := range taskIDSet {
		taskIDs = append(taskIDs, id)
	}
	userIDs := make([]uint, 0, len(userIDSet))
	for id := range userIDSet {
		userIDs = append(userIDs, id)
	}

	// Collect unique stages and due dates for tighter SQL filtering
	stageSet := make(map[models.ReminderStage]bool)
	dueDateSet := make(map[string]bool)
	var minDueDate, maxDueDate time.Time
	for _, k := range keys {
		stageSet[k.Stage] = true
		dueDateOnly := time.Date(k.DueDate.Year(), k.DueDate.Month(), k.DueDate.Day(), 0, 0, 0, 0, time.UTC)
		dueDateSet[dueDateOnly.Format("2006-01-02")] = true
		if minDueDate.IsZero() || dueDateOnly.Before(minDueDate) {
			minDueDate = dueDateOnly
		}
		if maxDueDate.IsZero() || dueDateOnly.After(maxDueDate) {
			maxDueDate = dueDateOnly
		}
	}
	stages := make([]models.ReminderStage, 0, len(stageSet))
	for s := range stageSet {
		stages = append(stages, s)
	}

	// Query matching reminder logs with tighter filters to reduce result set.
	// Filter on reminder_stage and due_date range in addition to task_id/user_id.
	var logs []models.TaskReminderLog
	err := r.db.Where(
		"task_id IN ? AND user_id IN ? AND reminder_stage IN ? AND due_date >= ? AND due_date <= ?",
		taskIDs, userIDs, stages, minDueDate, maxDueDate,
	).Find(&logs).Error
	if err != nil {
		return nil, err
	}

	// Match returned logs against our key set
	for _, l := range logs {
		dueDateStr := l.DueDate.Format("2006-01-02")
		nk := normalizedKey{
			TaskID:  l.TaskID,
			UserID:  l.UserID,
			DueDate: dueDateStr,
			Stage:   l.ReminderStage,
		}
		if indices, ok := keyToIdx[nk]; ok {
			for _, idx := range indices {
				result[idx] = true
			}
		}
	}

	return result, nil
}

// LogReminder records that a reminder was sent.
// Returns the created log entry or an error if the reminder was already sent
// (unique constraint violation).
func (r *ReminderRepository) LogReminder(taskID, userID uint, dueDate time.Time, stage models.ReminderStage, notificationID *uint) (*models.TaskReminderLog, error) {
	// Normalize to date only
	dueDateOnly := time.Date(dueDate.Year(), dueDate.Month(), dueDate.Day(), 0, 0, 0, 0, time.UTC)

	log := &models.TaskReminderLog{
		TaskID:         taskID,
		UserID:         userID,
		DueDate:        dueDateOnly,
		ReminderStage:  stage,
		SentAt:         time.Now().UTC(),
		NotificationID: notificationID,
	}

	err := r.db.Create(log).Error
	if err != nil {
		return nil, err
	}

	return log, nil
}

// GetSentRemindersForTask returns all reminder logs for a specific task and user.
func (r *ReminderRepository) GetSentRemindersForTask(taskID, userID uint) ([]models.TaskReminderLog, error) {
	var logs []models.TaskReminderLog
	err := r.db.Where("task_id = ? AND user_id = ?", taskID, userID).
		Order("sent_at DESC").
		Find(&logs).Error
	return logs, err
}

// GetSentRemindersForDueDate returns all reminder logs for a specific task,
// user, and due date.
func (r *ReminderRepository) GetSentRemindersForDueDate(taskID, userID uint, dueDate time.Time) ([]models.TaskReminderLog, error) {
	dueDateOnly := time.Date(dueDate.Year(), dueDate.Month(), dueDate.Day(), 0, 0, 0, 0, time.UTC)

	var logs []models.TaskReminderLog
	err := r.db.Where("task_id = ? AND user_id = ? AND due_date = ?",
		taskID, userID, dueDateOnly).
		Order("sent_at DESC").
		Find(&logs).Error
	return logs, err
}

// CleanupOldLogs removes reminder logs older than the specified number of days.
// This helps keep the table from growing indefinitely.
func (r *ReminderRepository) CleanupOldLogs(daysOld int) (int64, error) {
	cutoff := time.Now().UTC().AddDate(0, 0, -daysOld)

	result := r.db.Where("sent_at < ?", cutoff).
		Delete(&models.TaskReminderLog{})

	return result.RowsAffected, result.Error
}

// GetRecentReminderStats returns statistics about recent reminders sent.
// Useful for admin/monitoring purposes.
func (r *ReminderRepository) GetRecentReminderStats(sinceHours int) (map[string]int64, error) {
	since := time.Now().UTC().Add(-time.Duration(sinceHours) * time.Hour)

	stats := make(map[string]int64)

	// Count by stage
	rows, err := r.db.Model(&models.TaskReminderLog{}).
		Select("reminder_stage, COUNT(*) as count").
		Where("sent_at >= ?", since).
		Group("reminder_stage").
		Rows()
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	for rows.Next() {
		var stage string
		var count int64
		if err := rows.Scan(&stage, &count); err != nil {
			return nil, err
		}
		stats[stage] = count
	}

	return stats, nil
}
