import { ref, computed, watch } from 'vue';
import { get, set } from 'idb-keyval';
import { useConnectionStore } from '../stores/connection';
import { SOCKET_EVENTS } from '@gala/shared/constants';
import { CONFIG } from '@gala/shared/constants';
import type { VoteCategory, VoteSubmitPayload, AckResponse } from '@gala/shared/types';

interface QueuedVote {
  localId: string;
  candidateId: string;
  category: VoteCategory;
  clientTimestamp: number;
  status: 'pending' | 'sending' | 'confirmed' | 'failed';
  retryCount: number;
  error?: string;
}

const QUEUE_STORAGE_KEY = 'vote_queue';

/**
 * Composable for optimistic UI voting with local queue
 */
export function useVoteQueue() {
  const connectionStore = useConnectionStore();

  // State
  const queue = ref<QueuedVote[]>([]);
  const isProcessing = ref(false);
  const lastSyncTime = ref<number>(0);

  // Computed
  const pendingVotes = computed(() => queue.value.filter((v) => v.status === 'pending'));
  const failedVotes = computed(() => queue.value.filter((v) => v.status === 'failed'));
  const hasPendingVotes = computed(() => pendingVotes.value.length > 0);

  /**
   * Load queue from IndexedDB
   */
  async function loadQueue(): Promise<void> {
    try {
      const stored = await get<QueuedVote[]>(QUEUE_STORAGE_KEY);
      if (stored && Array.isArray(stored)) {
        // Reset sending status to pending (in case of page refresh during send)
        queue.value = stored.map((v) => ({
          ...v,
          status: v.status === 'sending' ? 'pending' : v.status,
        }));

        // Process any pending votes
        if (connectionStore.isConnected) {
          processQueue();
        }
      }
    } catch (error) {
      console.error('[VoteQueue] Failed to load queue:', error);
    }
  }

  /**
   * Persist queue to IndexedDB
   */
  async function persistQueue(): Promise<void> {
    try {
      await set(QUEUE_STORAGE_KEY, queue.value);
    } catch (error) {
      console.error('[VoteQueue] Failed to persist queue:', error);
    }
  }

  /**
   * Generate a simple UUID
   */
  function generateLocalId(): string {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
      const r = (Math.random() * 16) | 0;
      const v = c === 'x' ? r : (r & 0x3) | 0x8;
      return v.toString(16);
    });
  }

  /**
   * Enqueue a vote (optimistic UI)
   * Returns the localId for tracking
   */
  function enqueue(candidateId: string, category: VoteCategory): string {
    const localId = generateLocalId();

    const vote: QueuedVote = {
      localId,
      candidateId,
      category,
      clientTimestamp: Date.now(),
      status: 'pending',
      retryCount: 0,
    };

    queue.value.push(vote);
    persistQueue();

    // Immediately try to process
    processQueue();

    return localId;
  }

  /**
   * Process the queue
   */
  async function processQueue(): Promise<void> {
    const socket = connectionStore.getSocket();
    if (isProcessing.value || !connectionStore.isConnected || !socket) {
      return;
    }

    const pending = queue.value.filter((v) => v.status === 'pending');
    if (pending.length === 0) {
      return;
    }

    isProcessing.value = true;

    for (const vote of pending) {
      await processVote(vote);
    }

    isProcessing.value = false;
    persistQueue();
  }

  /**
   * Process a single vote
   */
  async function processVote(vote: QueuedVote): Promise<void> {
    const socket = connectionStore.getSocket();
    if (!socket?.connected) {
      return;
    }

    vote.status = 'sending';

    const payload: VoteSubmitPayload = {
      localId: vote.localId,
      candidateId: vote.candidateId,
      category: vote.category,
      clientTimestamp: vote.clientTimestamp,
    };

    try {
      const response = await new Promise<AckResponse<{ newCount: number }>>((resolve, reject) => {
        const timeout = setTimeout(() => {
          reject(new Error('timeout'));
        }, 5000);

        socket.emit(
          SOCKET_EVENTS.VOTE_SUBMIT as any,
          payload,
          (response: AckResponse<{ newCount: number }>) => {
            clearTimeout(timeout);
            resolve(response);
          }
        );
      });

      if (response.success) {
        vote.status = 'confirmed';
        connectionStore.addVotedCategory(vote.category);
        console.log('[VoteQueue] Vote confirmed:', vote.localId);
      } else {
        handleVoteError(vote, response.error || 'UNKNOWN_ERROR');
      }
    } catch (error) {
      handleVoteError(vote, 'NETWORK_ERROR');
    }
  }

  /**
   * Handle vote error with retry logic
   */
  function handleVoteError(vote: QueuedVote, error: string): void {
    vote.retryCount++;
    vote.error = error;

    // Don't retry for certain errors
    const nonRetryableErrors = ['ALREADY_VOTED', 'MAX_VOTES_REACHED', 'INVALID_CANDIDATE', 'VOTING_CLOSED'];

    if (nonRetryableErrors.includes(error) || vote.retryCount >= CONFIG.VOTE_QUEUE_MAX_RETRIES) {
      vote.status = 'failed';
      console.warn('[VoteQueue] Vote failed permanently:', vote.localId, error);
    } else {
      vote.status = 'pending';
      console.log('[VoteQueue] Vote will retry:', vote.localId, `(attempt ${vote.retryCount})`);

      // Schedule retry with exponential backoff
      const delay = CONFIG.VOTE_QUEUE_RETRY_DELAY_MS * Math.pow(2, vote.retryCount - 1);
      setTimeout(() => processQueue(), delay);
    }
  }

  /**
   * Get vote status by localId
   */
  function getVoteStatus(localId: string): QueuedVote | undefined {
    return queue.value.find((v) => v.localId === localId);
  }

  /**
   * Check if a category has a pending/confirmed vote
   */
  function hasVoteForCategory(category: VoteCategory): boolean {
    return queue.value.some(
      (v) => v.category === category && (v.status === 'pending' || v.status === 'sending' || v.status === 'confirmed')
    );
  }

  /**
   * Clear confirmed votes from queue
   */
  function clearConfirmed(): void {
    queue.value = queue.value.filter((v) => v.status !== 'confirmed');
    persistQueue();
  }

  /**
   * Retry failed votes
   */
  function retryFailed(): void {
    for (const vote of queue.value) {
      if (vote.status === 'failed') {
        vote.status = 'pending';
        vote.retryCount = 0;
        vote.error = undefined;
      }
    }
    persistQueue();
    processQueue();
  }

  /**
   * Remove a specific vote from queue
   */
  function removeVote(localId: string): void {
    const index = queue.value.findIndex((v) => v.localId === localId);
    if (index !== -1) {
      queue.value.splice(index, 1);
      persistQueue();
    }
  }

  // Watch connection status to process queue when reconnected
  watch(
    () => connectionStore.isConnected,
    (connected) => {
      if (connected && hasPendingVotes.value) {
        console.log('[VoteQueue] Connection restored, processing pending votes');
        processQueue();
      }
    }
  );

  // Load queue on init
  loadQueue();

  return {
    // State
    queue,
    isProcessing,
    lastSyncTime,

    // Computed
    pendingVotes,
    failedVotes,
    hasPendingVotes,

    // Actions
    enqueue,
    processQueue,
    getVoteStatus,
    hasVoteForCategory,
    clearConfirmed,
    retryFailed,
    removeVote,
  };
}
