package scheduling

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

	"crussell/db"
	"crussell/mw"

	"github.com/go-chi/chi/v5"
	"github.com/jackc/pgx/v5"
	"github.com/robfig/cron/v3"
)

// --- Types ---

type TimeBlocker struct {
	ID              string    `json:"id"`
	StartTime       time.Time `json:"start_time"`
	DurationMinutes int       `json:"duration_minutes"`
	Description     string    `json:"description,omitempty"`
	CronExpression  *string   `json:"cron_expression,omitempty"`
	CreatedAt       time.Time `json:"created_at"`
	CreatedBy       *string   `json:"created_by,omitempty"`
}

type CreateTimeBlockerRequest struct {
	StartTime       time.Time `json:"start_time"`
	DurationMinutes int       `json:"duration_minutes"`
	Description     string    `json:"description,omitempty"`
	CronExpression  *string   `json:"cron_expression,omitempty"`
}

// --- List Time Blockers ---
// GET /api/admin/time-blockers
// Returns:
// - Future one-off blockers (cron_expression IS NULL AND start_time >= now)
// - ALL recurring blockers (cron_expression IS NOT NULL)
func ListTimeBlockers(w http.ResponseWriter, r *http.Request) {
	// Optional date range filtering
	startStr := r.URL.Query().Get("start")
	endStr := r.URL.Query().Get("end")

	var rows pgx.Rows
	var err error

	if startStr != "" && endStr != "" {
		// Filter by date range
		ukLocation, _ := time.LoadLocation("Europe/London")
		start, err1 := time.ParseInLocation("2006-01-02", startStr, ukLocation)
		end, err2 := time.ParseInLocation("2006-01-02", endStr, ukLocation)
		if err1 != nil || err2 != nil {
			http.Error(w, "invalid date format, expected YYYY-MM-DD", http.StatusBadRequest)
			return
		}
		start = time.Date(start.Year(), start.Month(), start.Day(), 0, 0, 0, 0, ukLocation)
		end = time.Date(end.Year(), end.Month(), end.Day(), 23, 59, 59, 999999999, ukLocation)

		// Get one-off blockers in range + ALL recurring blockers
		rows, err = db.DB.Query(r.Context(), `
			SELECT id, start_time, duration_minutes, description, cron_expression, created_at, created_by
			FROM time_blockers
			WHERE (cron_expression IS NULL AND start_time >= $1 AND start_time <= $2)
			   OR (cron_expression IS NOT NULL)
			ORDER BY 
				CASE WHEN cron_expression IS NULL THEN 0 ELSE 1 END,
				start_time DESC
		`, start, end)
	} else {
		// Get future one-off blockers + ALL recurring blockers
		now := time.Now()
		rows, err = db.DB.Query(r.Context(), `
			SELECT id, start_time, duration_minutes, description, cron_expression, created_at, created_by
			FROM time_blockers
			WHERE (cron_expression IS NULL AND start_time >= $1)
			   OR (cron_expression IS NOT NULL)
			ORDER BY 
				CASE WHEN cron_expression IS NULL THEN 0 ELSE 1 END,
				start_time DESC
			LIMIT 100
		`, now)
	}

	if err != nil {
		http.Error(w, "failed to fetch time blockers", http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	var blockers []TimeBlocker
	for rows.Next() {
		var b TimeBlocker
		if err := rows.Scan(&b.ID, &b.StartTime, &b.DurationMinutes, &b.Description, &b.CronExpression, &b.CreatedAt, &b.CreatedBy); err != nil {
			http.Error(w, "failed to scan time blocker", http.StatusInternalServerError)
			return
		}
		blockers = append(blockers, b)
	}

	if err := rows.Err(); err != nil {
		http.Error(w, "error iterating time blockers", http.StatusInternalServerError)
		return
	}

	if blockers == nil {
		blockers = []TimeBlocker{}
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(blockers)
}

// --- Create Time Blocker ---
// POST /api/admin/time-blockers
func CreateTimeBlocker(w http.ResponseWriter, r *http.Request) {
	var req CreateTimeBlockerRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "invalid JSON: "+err.Error(), http.StatusBadRequest)
		return
	}

	// Validate required fields
	if req.StartTime.IsZero() {
		http.Error(w, "start_time is required", http.StatusBadRequest)
		return
	}
	if req.DurationMinutes <= 0 {
		http.Error(w, "duration_minutes must be greater than 0", http.StatusBadRequest)
		return
	}

	// Get admin user ID from context
	var createdBy *string
	if userID, ok := r.Context().Value(mw.UserIDKey).(string); ok {
		createdBy = &userID
	}

	// Insert the time blocker
	var blocker TimeBlocker
	err := db.DB.QueryRow(r.Context(), `
		INSERT INTO time_blockers (start_time, duration_minutes, description, cron_expression, created_by)
		VALUES ($1, $2, $3, $4, $5)
		RETURNING id, start_time, duration_minutes, description, cron_expression, created_at, created_by
	`, req.StartTime, req.DurationMinutes, req.Description, req.CronExpression, createdBy).Scan(
		&blocker.ID, &blocker.StartTime, &blocker.DurationMinutes, &blocker.Description,
		&blocker.CronExpression, &blocker.CreatedAt, &blocker.CreatedBy,
	)
	if err != nil {
		http.Error(w, "failed to create time blocker", http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusCreated)
	json.NewEncoder(w).Encode(blocker)
}

// --- Delete Time Blocker ---
// DELETE /api/admin/time-blockers/{id}
func DeleteTimeBlocker(w http.ResponseWriter, r *http.Request) {
	id := chi.URLParam(r, "id")
	if id == "" {
		http.Error(w, "missing id parameter", http.StatusBadRequest)
		return
	}

	result, err := db.DB.Exec(r.Context(), `
		DELETE FROM time_blockers WHERE id = $1
	`, id)
	if err != nil {
		http.Error(w, "failed to delete time blocker", http.StatusInternalServerError)
		return
	}

	rowsAffected := result.RowsAffected()
	if rowsAffected == 0 {
		http.Error(w, "time blocker not found", http.StatusNotFound)
		return
	}

	w.WriteHeader(http.StatusNoContent)
}

// --- Helper: Get Time Blockers in Range ---

// --- Helper: Get Time Blockers in Range ---
// Returns blockers for the given date range, expanded for recurring blockers
// Used by GetAvailableHours to subtract blocked time from available slots
func GetTimeBlockersInRange(ctx context.Context, start, end time.Time) ([]TimeBlocker, error) {
	// Get one-off blockers in range
	rows, err := db.DB.Query(ctx, `
		SELECT id, start_time, duration_minutes, description, cron_expression, created_at, created_by
		FROM time_blockers
		WHERE cron_expression IS NULL
		AND description NOT LIKE 'RESERVATION:%'
		AND start_time >= $1 AND start_time <= $2
		ORDER BY start_time
	`, start, end)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var blockers []TimeBlocker
	for rows.Next() {
		var b TimeBlocker
		if err := rows.Scan(&b.ID, &b.StartTime, &b.DurationMinutes, &b.Description, &b.CronExpression, &b.CreatedAt, &b.CreatedBy); err != nil {
			return nil, err
		}
		blockers = append(blockers, b)
	}
	rows.Close()

	// Get ALL recurring blockers and expand them
	recurringRows, err := db.DB.Query(ctx, `
		SELECT id, start_time, duration_minutes, description, cron_expression, created_at, created_by
		FROM time_blockers
		WHERE cron_expression IS NOT NULL
	`)
	if err != nil {
		return nil, err
	}
	defer recurringRows.Close()

	for recurringRows.Next() {
		var b TimeBlocker
		if err := recurringRows.Scan(&b.ID, &b.StartTime, &b.DurationMinutes, &b.Description, &b.CronExpression, &b.CreatedAt, &b.CreatedBy); err != nil {
			return nil, err
		}

		// Expand recurring blocker to occurrences within range
		occurrences := expandCronOccurrences(b, start, end)
		blockers = append(blockers, occurrences...)
	}

	return blockers, nil
}

// expandCronOccurrences expands a recurring blocker to all occurrences within a date range
// The cron expression defines the pattern, and the blocker's start_time provides the time-of-day
func expandCronOccurrences(blocker TimeBlocker, rangeStart, rangeEnd time.Time) []TimeBlocker {
	if blocker.CronExpression == nil {
		return nil
	}

	parser := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow)
	schedule, err := parser.Parse(*blocker.CronExpression)
	if err != nil {
		log.Printf("Invalid cron expression '%s': %v", *blocker.CronExpression, err)
		return nil
	}

	// Get the time-of-day from the blocker's start_time
	blockerHour := blocker.StartTime.Hour()
	blockerMinute := blocker.StartTime.Minute()

	// Use UK timezone for expansion
	ukLocation, _ := time.LoadLocation("Europe/London")

	var occurrences []TimeBlocker

	// Start from the beginning of the range
	current := time.Date(rangeStart.Year(), rangeStart.Month(), rangeStart.Day(), blockerHour, blockerMinute, 0, 0, ukLocation)

	// Find the first occurrence on or after rangeStart
	firstNext := schedule.Next(current.Add(-time.Second))
	if firstNext.Before(rangeStart) {
		current = schedule.Next(firstNext)
	} else {
		current = firstNext
	}

	// Collect all occurrences within the range
	for current.Before(rangeEnd) || current.Equal(rangeEnd) {
		// Create a new blocker instance for this occurrence
		occurrence := TimeBlocker{
			ID:              blocker.ID,
			StartTime:       current,
			DurationMinutes: blocker.DurationMinutes,
			Description:     blocker.Description,
			CronExpression:  blocker.CronExpression,
			CreatedAt:       blocker.CreatedAt,
			CreatedBy:       blocker.CreatedBy,
		}
		occurrences = append(occurrences, occurrence)

		// Get next occurrence
		next := schedule.Next(current)
		if next.Equal(current) {
			break // Prevent infinite loop if schedule isn't advancing
		}
		current = next
	}

	return occurrences
}

// --- Helper: Check Time Blocker Overlap ---
// Returns (hasOverlap, blockerDescription, error)
// Used by booking handlers to check for blocker conflicts
func CheckTimeBlockerOverlap(ctx context.Context, startTime, endTime time.Time) (bool, string, error) {
	// Get all blockers in an expanded range that could overlap
	// We need to look further back because recurring blockers could span multiple periods
	searchStart := startTime.AddDate(0, -1, 0) // Look back 1 month for recurring patterns
	searchEnd := endTime

	blockers, err := GetTimeBlockersInRange(ctx, searchStart, searchEnd)
	if err != nil {
		return false, "", err
	}

	// Check each blocker (one-off or expanded recurring) for overlap
	for _, blocker := range blockers {
		blockerEnd := blocker.StartTime.Add(time.Duration(blocker.DurationMinutes) * time.Minute)

		// Check if the booking overlaps with the blocker
		// Overlap condition: booking_start < blocker_end AND booking_end > blocker_start
		if startTime.Before(blockerEnd) && endTime.After(blocker.StartTime) {
			desc := blocker.Description
			if desc == "" {
				desc = "Time blocked"
			}
			if blocker.CronExpression != nil {
				desc = fmt.Sprintf("%s (recurring: %s)", desc, *blocker.CronExpression)
			}
			return true, desc, nil
		}
	}

	return false, "", nil
}

// CleanupOldReservations deletes expired reservations:
// - Logged-in (RESERVATION:user): older than 1 hour
// - Anonymous (RESERVATION:anon): older than 10 minutes
// - Admin walk-in (RESERVATION:admin:walkin:%): older than 15 minutes
// - Admin call-in (RESERVATION:admin:callin:%): older than 15 minutes
func CleanupOldReservations(ctx context.Context) error {
	oneHourAgo := time.Now().Add(-1 * time.Hour)
	tenMinutesAgo := time.Now().Add(-10 * time.Minute)
	fifteenMinutesAgo := time.Now().Add(-15 * time.Minute)
	twentyFourHoursAgo := time.Now().Add(-24 * time.Hour)

	_, err := db.DB.Exec(ctx, `
		DELETE FROM time_blockers
		WHERE (description LIKE 'RESERVATION:user:%' AND created_at < $1)
		   OR (description LIKE 'RESERVATION:anon:%' AND created_at < $2)
		   OR (description LIKE 'RESERVATION:admin:walkin:%' AND created_at < $3)
		   OR (description LIKE 'RESERVATION:admin:callin:%' AND created_at < $3)
		   OR (description LIKE 'RESERVATION:edit_request:%' AND created_at < $4)
	`, oneHourAgo, tenMinutesAgo, fifteenMinutesAgo, twentyFourHoursAgo)
	return err
}

// AnonymizeStaleGuestAccounts anonymizes personal data for guest accounts
// whose last booking was more than 6 months ago (UK GDPR storage limitation).
// Financial records (bookings, payments) remain intact — only PII is scrubbed.
// Active/pending bookings are excluded so the salon can still contact the guest.
func AnonymizeStaleGuestAccounts(ctx context.Context) error {
	_, err := db.DB.Exec(ctx, `
		UPDATE users SET
			n_first_name = 'Guest',
			n_last_name = 'Anonymized',
			email = 'anon-' || id || '@anon.invalid',
			phone = '000000000000',
			date_of_birth = '1900-01-01',
			updated_at = NOW()
		WHERE account_role = 'guest'
		  AND id NOT IN (
			  SELECT user_id FROM bookings WHERE status IN ('pending', 'confirmed')
		  )
		  AND id IN (
			  SELECT user_id
			  FROM bookings
			  WHERE user_id IS NOT NULL
			  GROUP BY user_id
			  HAVING MAX(start_time) < NOW() - INTERVAL '6 months'
		  )
	`)
	return err
}
