package scheduling

import (
	"context"
	"fmt"
	"log/slog"

	"crussell/db"
)

// NotifyUnpaidOneWeek inserts admin_notifications for bookings that ended
// 7+ days ago with no completed payment. Runs daily.
// TODO: Notify affected user via email/SMS when SMTP is wired (E5).
func NotifyUnpaidOneWeek(ctx context.Context) (int, error) {
	tx, err := db.Conn.Begin(ctx)
	if err != nil {
		return 0, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer func() {
    if err := tx.Rollback(ctx); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	rows, err := tx.Query(ctx, `
		SELECT b.id, b.user_id
		FROM bookings b
		WHERE b.status NOT IN ('client_cancelled', 'we_cancelled')
		  AND NOT EXISTS (
		      SELECT 1 FROM payments p
		      WHERE p.booking_id = b.id AND p.status = 'completed'
		  )
		  AND b.end_time >= NOW() - INTERVAL '30 days'
		  AND b.end_time < NOW() - INTERVAL '7 days'
		  AND NOT EXISTS (
		      SELECT 1 FROM admin_notifications an
		      WHERE an.booking_id = b.id AND an.reason = '1_week_no_pay'
		  )
	`)
	if err != nil {
		return 0, fmt.Errorf("failed to query unpaid 1-week bookings: %w", err)
	}
	defer rows.Close()

	var ids, userIDs []string
	for rows.Next() {
		var id, userID string
		if err := rows.Scan(&id, &userID); err != nil {
			return 0, fmt.Errorf("failed to scan row: %w", err)
		}
		ids = append(ids, id)
		userIDs = append(userIDs, userID)
	}
	if err := rows.Err(); err != nil {
		return 0, fmt.Errorf("rows iteration error: %w", err)
	}

	if len(ids) == 0 {
		if err := tx.Commit(ctx); err != nil {
			return 0, fmt.Errorf("failed to commit: %w", err)
		}
		return 0, nil
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO admin_notifications (reason, booking_id, user_id)
		SELECT '1_week_no_pay', unnest($1::text[]), unnest($2::text[])
	`, ids, userIDs)
	if err != nil {
		return 0, fmt.Errorf("failed to insert 1_week_no_pay notifications: %w", err)
	}

	if err := tx.Commit(ctx); err != nil {
		return 0, fmt.Errorf("failed to commit: %w", err)
	}

	return len(ids), nil
}

// NotifyUnpaidOneMonth inserts admin_notifications for bookings that ended
// 30+ days ago with no completed payment. Runs daily.
// TODO: Notify affected user via email/SMS when SMTP is wired (E5).
func NotifyUnpaidOneMonth(ctx context.Context) (int, error) {
	tx, err := db.Conn.Begin(ctx)
	if err != nil {
		return 0, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer func() {
    if err := tx.Rollback(ctx); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	rows, err := tx.Query(ctx, `
		SELECT b.id, b.user_id
		FROM bookings b
		WHERE b.status NOT IN ('client_cancelled', 'we_cancelled')
		  AND NOT EXISTS (
		      SELECT 1 FROM payments p
		      WHERE p.booking_id = b.id AND p.status = 'completed'
		  )
		  AND b.end_time < NOW() - INTERVAL '30 days'
		  AND NOT EXISTS (
		      SELECT 1 FROM admin_notifications an
		      WHERE an.booking_id = b.id AND an.reason = '1_month_no_pay'
		  )
	`)
	if err != nil {
		return 0, fmt.Errorf("failed to query unpaid 1-month bookings: %w", err)
	}
	defer rows.Close()

	var ids, userIDs []string
	for rows.Next() {
		var id, userID string
		if err := rows.Scan(&id, &userID); err != nil {
			return 0, fmt.Errorf("failed to scan row: %w", err)
		}
		ids = append(ids, id)
		userIDs = append(userIDs, userID)
	}
	if err := rows.Err(); err != nil {
		return 0, fmt.Errorf("rows iteration error: %w", err)
	}

	if len(ids) == 0 {
		if err := tx.Commit(ctx); err != nil {
			return 0, fmt.Errorf("failed to commit: %w", err)
		}
		return 0, nil
	}

	_, err = tx.Exec(ctx, `
		INSERT INTO admin_notifications (reason, booking_id, user_id)
		SELECT '1_month_no_pay', unnest($1::text[]), unnest($2::text[])
	`, ids, userIDs)
	if err != nil {
		return 0, fmt.Errorf("failed to insert 1_month_no_pay notifications: %w", err)
	}

	if err := tx.Commit(ctx); err != nil {
		return 0, fmt.Errorf("failed to commit: %w", err)
	}

	return len(ids), nil
}

// TransitionDiscountCampaigns auto-transitions campaign statuses based on
// dates and redemption limits:
//   - draft → active when start_date <= NOW()
//   - active → completed when end_date < NOW() or max_redemptions reached
func TransitionDiscountCampaigns(ctx context.Context) (int, error) {
	tx, err := db.Conn.Begin(ctx)
	if err != nil {
		return 0, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer func() {
    if err := tx.Rollback(ctx); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	result, err := tx.Exec(ctx, `
		UPDATE discount_campaigns
		SET status = 'active'
		WHERE status = 'draft'
		  AND campaign_type = 'time_based'
		  AND start_date <= NOW()
	`)
	if err != nil {
		return 0, fmt.Errorf("failed to activate campaigns: %w", err)
	}
	activated := result.RowsAffected()

	result, err = tx.Exec(ctx, `
		UPDATE discount_campaigns
		SET status = 'completed'
		WHERE status = 'active'
		  AND (
		      end_date < NOW()
		      OR (max_redemptions IS NOT NULL AND times_redeemed >= max_redemptions)
		  )
	`)
	if err != nil {
		return 0, fmt.Errorf("failed to complete campaigns: %w", err)
	}
	completed := result.RowsAffected()

	if err := tx.Commit(ctx); err != nil {
		return 0, fmt.Errorf("failed to commit: %w", err)
	}

	return int(activated + completed), nil
}

// CleanupExpiredVerificationCodes deletes expired verification codes and
// used codes older than 30 days.
func CleanupExpiredVerificationCodes(ctx context.Context) (int, error) {
	tx, err := db.Conn.Begin(ctx)
	if err != nil {
		return 0, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer func() {
    if err := tx.Rollback(ctx); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	result, err := tx.Exec(ctx, `
		DELETE FROM verification_codes
		WHERE (used_at IS NOT NULL AND used_at < NOW() - INTERVAL '30 days')
		   OR (expires_at < NOW() AND used_at IS NULL)
	`)
	if err != nil {
		return 0, fmt.Errorf("failed to cleanup verification codes: %w", err)
	}

	if err := tx.Commit(ctx); err != nil {
		return 0, fmt.Errorf("failed to commit: %w", err)
	}

	return int(result.RowsAffected()), nil
}

// CleanupExpiredRefreshTokens deletes expired refresh tokens and
// revoked tokens older than 90 days.
func CleanupExpiredRefreshTokens(ctx context.Context) (int, error) {
	tx, err := db.Conn.Begin(ctx)
	if err != nil {
		return 0, fmt.Errorf("failed to begin transaction: %w", err)
	}
	defer func() {
    if err := tx.Rollback(ctx); err != nil {
        slog.Error("failed to rollback transaction", "err", err)
    }
}()

	result, err := tx.Exec(ctx, `
		DELETE FROM refresh_tokens
		WHERE expires_at < NOW()
		   OR (revoked = TRUE AND created_at < NOW() - INTERVAL '90 days')
	`)
	if err != nil {
		return 0, fmt.Errorf("failed to cleanup refresh tokens: %w", err)
	}

	if err := tx.Commit(ctx); err != nil {
		return 0, fmt.Errorf("failed to commit: %w", err)
	}

	return int(result.RowsAffected()), nil
}
