package services

import (
	"context"
	"fmt"
	"math"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
	"ticketing-backend/internal/config"
	"ticketing-backend/internal/models"
)

type QueueService struct {
	pool     *pgxpool.Pool
	settings *config.AppSettings
}

func NewQueueService(pool *pgxpool.Pool, settings *config.AppSettings) *QueueService {
	return &QueueService{pool: pool, settings: settings}
}

func (s *QueueService) EnqueueUser(eventID, userID, ipAddress string) (*models.QueueEntry, error) {
	ctx := context.Background()

	// Mark expired sessions
	_, _ = s.pool.Exec(ctx,
		`UPDATE queue_entries SET status = 'expired'
		 WHERE event_id = $1 AND status = 'active' AND expires_at < NOW()`,
		eventID,
	)

	// Check if user already has active/waiting entry
	var existing models.QueueEntry
	err := s.pool.QueryRow(ctx,
		`SELECT id, event_id, user_id, position, status, entered_at, activated_at, expires_at
		 FROM queue_entries
		 WHERE event_id = $1 AND user_id = $2 AND status IN ('waiting', 'active')
		 AND (status = 'waiting' OR expires_at > NOW())
		 ORDER BY entered_at DESC LIMIT 1`,
		eventID, userID,
	).Scan(&existing.ID, &existing.EventID, &existing.UserID, &existing.Position,
		&existing.Status, &existing.EnteredAt, &existing.ActivatedAt, &existing.ExpiresAt)

	if err == nil {
		if existing.Status == "active" && existing.ExpiresAt != nil && existing.ExpiresAt.After(time.Now()) {
			return &existing, nil
		}
		if existing.Status == "waiting" {
			return &existing, nil
		}
		_, _ = s.pool.Exec(ctx,
			`UPDATE queue_entries SET status = 'expired' WHERE id = $1`, existing.ID,
		)
	}

	// Get next position
	var nextPos int
	err = s.pool.QueryRow(ctx,
		`SELECT COALESCE(MAX(position), 0) + 1 FROM queue_entries
		 WHERE event_id = $1 AND status IN ('waiting', 'active')`,
		eventID,
	).Scan(&nextPos)
	if err != nil {
		nextPos = 1
	}

	// Create entry
	var entry models.QueueEntry
	err = s.pool.QueryRow(ctx,
		`INSERT INTO queue_entries (event_id, user_id, position, ip_address, status)
		 VALUES ($1, $2, $3, $4, 'waiting')
		 RETURNING id, event_id, user_id, position, status, entered_at`,
		eventID, userID, nextPos, ipAddress,
	).Scan(&entry.ID, &entry.EventID, &entry.UserID, &entry.Position,
		&entry.Status, &entry.EnteredAt)
	if err != nil {
		return nil, fmt.Errorf("failed to create queue entry: %w", err)
	}

	// Try to auto-activate
	s.tryActivateNextUsers(ctx, eventID)

	// Re-fetch to get updated status
	s.pool.QueryRow(ctx,
		`SELECT id, event_id, user_id, position, status, entered_at, activated_at, expires_at
		 FROM queue_entries WHERE id = $1`, entry.ID,
	).Scan(&entry.ID, &entry.EventID, &entry.UserID, &entry.Position,
		&entry.Status, &entry.EnteredAt, &entry.ActivatedAt, &entry.ExpiresAt)

	return &entry, nil
}

func (s *QueueService) tryActivateNextUsers(ctx context.Context, eventID string) {
	var activeCount int
	s.pool.QueryRow(ctx,
		`SELECT COUNT(*) FROM queue_entries
		 WHERE event_id = $1 AND status = 'active' AND expires_at > NOW()`,
		eventID,
	).Scan(&activeCount)

	maxSessions := s.settings.GetQueueMaxActiveSessions()
	slotsAvailable := maxSessions - activeCount
	if slotsAvailable <= 0 {
		return
	}

	rows, err := s.pool.Query(ctx,
		`SELECT id, user_id FROM queue_entries
		 WHERE event_id = $1 AND status = 'waiting'
		 ORDER BY position ASC LIMIT $2`,
		eventID, slotsAvailable,
	)
	if err != nil {
		return
	}
	defer rows.Close()

	duration := s.settings.GetQueueSessionDuration()
	expiresAt := time.Now().Add(time.Duration(duration) * time.Second)
	for rows.Next() {
		var id, userID string
		rows.Scan(&id, &userID)
		s.pool.Exec(ctx,
			`UPDATE queue_entries SET status = 'active', activated_at = NOW(), expires_at = $2
			 WHERE id = $1 AND status = 'waiting'`,
			id, expiresAt,
		)
	}
}

func (s *QueueService) GetQueueStatus(eventID, userID string) (*models.QueueEntry, error) {
	ctx := context.Background()

	// Clean up expired sessions first
	_, _ = s.pool.Exec(ctx,
		`UPDATE queue_entries SET status = 'expired'
		 WHERE event_id = $1 AND status = 'active' AND expires_at < NOW()`,
		eventID,
	)

	// Try to activate waiting users
	s.tryActivateNextUsers(ctx, eventID)

	var entry models.QueueEntry
	err := s.pool.QueryRow(ctx,
		`SELECT id, event_id, user_id, position, status, entered_at, activated_at, expires_at
		 FROM queue_entries
		 WHERE event_id = $1 AND user_id = $2 AND status IN ('waiting', 'active')
		 ORDER BY entered_at DESC LIMIT 1`,
		eventID, userID,
	).Scan(&entry.ID, &entry.EventID, &entry.UserID, &entry.Position,
		&entry.Status, &entry.EnteredAt, &entry.ActivatedAt, &entry.ExpiresAt)

	if err != nil {
		return nil, nil
	}

	// Double-check if active session is expired
	if entry.Status == "active" && entry.ExpiresAt != nil && entry.ExpiresAt.Before(time.Now()) {
		s.pool.Exec(ctx, `UPDATE queue_entries SET status = 'expired' WHERE id = $1`, entry.ID)
		return nil, nil
	}

	return &entry, nil
}

func (s *QueueService) ActivateSession(eventID, userID string) error {
	ctx := context.Background()
	duration := s.settings.GetQueueSessionDuration()
	expiresAt := time.Now().Add(time.Duration(duration) * time.Second)

	result, err := s.pool.Exec(ctx,
		`UPDATE queue_entries SET status = 'active', activated_at = NOW(), expires_at = $3
		 WHERE event_id = $1 AND user_id = $2 AND status = 'waiting'`,
		eventID, userID, expiresAt,
	)
	if err != nil {
		return err
	}
	if result.RowsAffected() == 0 {
		return fmt.Errorf("queue entry not found")
	}
	return nil
}

func (s *QueueService) IsSessionActive(eventID, userID string) (bool, error) {
	ctx := context.Background()
	var exists bool
	err := s.pool.QueryRow(ctx,
		`SELECT EXISTS(SELECT 1 FROM queue_entries
		 WHERE event_id = $1 AND user_id = $2 AND status = 'active' AND expires_at > NOW())`,
		eventID, userID,
	).Scan(&exists)
	return exists, err
}

func (s *QueueService) ReleaseUser(eventID, userID string, newStatus string) error {
	_, err := s.pool.Exec(context.Background(),
		`UPDATE queue_entries SET status = $3, completed_at = NOW()
		 WHERE event_id = $1 AND user_id = $2 AND status IN ('waiting', 'active')`,
		eventID, userID, newStatus,
	)
	return err
}

func (s *QueueService) ProcessExpiredSessions(eventID string) (int, error) {
	result, err := s.pool.Exec(context.Background(),
		`UPDATE queue_entries SET status = 'expired', completed_at = NOW()
		 WHERE event_id = $1 AND status = 'active' AND expires_at < NOW()`,
		eventID,
	)
	if err != nil {
		return 0, err
	}
	return int(result.RowsAffected()), nil
}

func (s *QueueService) GetNextInQueue(eventID string, limit int) ([]models.QueueEntry, error) {
	rows, err := s.pool.Query(context.Background(),
		`SELECT id, user_id, position FROM queue_entries
		 WHERE event_id = $1 AND status = 'waiting'
		 ORDER BY position ASC LIMIT $2`,
		eventID, limit,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var entries []models.QueueEntry
	for rows.Next() {
		var e models.QueueEntry
		rows.Scan(&e.ID, &e.UserID, &e.Position)
		entries = append(entries, e)
	}
	return entries, nil
}

func (s *QueueService) EstimateWaitTime(eventID string, position int) int {
	ctx := context.Background()
	var waitingAhead int
	s.pool.QueryRow(ctx,
		`SELECT COUNT(*) FROM queue_entries
		 WHERE event_id = $1 AND position < $2 AND status = 'waiting'`,
		eventID, position,
	).Scan(&waitingAhead)

	duration := s.settings.GetQueueSessionDuration()
	return int(math.Ceil(float64(waitingAhead*duration) / 60))
}
