/**
 * Batch Progress Store
 *
 * Pinia store for tracking bulk upload batch progress via long-polling.
 * Uses AbortController for clean cancellation on component unmount.
 *
 * US-009: Added localStorage persistence for active batch IDs to support
 * auto-resume polling after page refresh or navigation away.
 */

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import api from '@data-entry/services/api'

/**
 * @typedef {Object} JobStatus
 * @property {string} job_id - OCR job UUID
 * @property {string} filename - Original filename
 * @property {string} status - Job status: 'pending' | 'processing' | 'completed' | 'failed'
 * @property {number|null} receipt_id - Created receipt ID (if completed)
 * @property {string|null} error_message - Error message (if failed)
 */

// US-009: localStorage key for persisting active batch IDs
const ACTIVE_BATCHES_STORAGE_KEY = 'roa2web_active_batch_ids'

/**
 * Get active batch IDs from localStorage
 * @returns {string[]} Array of batch ID strings
 */
function getStoredActiveBatches() {
  try {
    const stored = localStorage.getItem(ACTIVE_BATCHES_STORAGE_KEY)
    if (!stored) return []
    const parsed = JSON.parse(stored)
    return Array.isArray(parsed) ? parsed : []
  } catch (e) {
    console.warn('[BatchProgress] Failed to read localStorage:', e)
    return []
  }
}

/**
 * Save active batch IDs to localStorage
 * @param {string[]} batchIds - Array of batch ID strings
 */
function saveActiveBatches(batchIds) {
  try {
    if (batchIds.length === 0) {
      localStorage.removeItem(ACTIVE_BATCHES_STORAGE_KEY)
    } else {
      localStorage.setItem(ACTIVE_BATCHES_STORAGE_KEY, JSON.stringify(batchIds))
    }
  } catch (e) {
    console.warn('[BatchProgress] Failed to save to localStorage:', e)
  }
}

/**
 * Add a batch ID to localStorage
 * @param {string} batchId - Batch ID to add
 */
function addActiveBatch(batchId) {
  const batches = getStoredActiveBatches()
  if (!batches.includes(batchId)) {
    batches.push(batchId)
    saveActiveBatches(batches)
  }
}

/**
 * Remove a batch ID from localStorage
 * @param {string} batchId - Batch ID to remove
 */
function removeActiveBatch(batchId) {
  const batches = getStoredActiveBatches()
  const filtered = batches.filter(id => id !== batchId)
  saveActiveBatches(filtered)
}

export const useBatchProgressStore = defineStore('batchProgress', () => {
  // ============ State ============

  /** @type {import('vue').Ref<number|null>} Current batch ID being tracked */
  const batchId = ref(null)

  /** @type {import('vue').Ref<Map<string, JobStatus>>} Map of job_id -> JobStatus */
  const jobs = ref(new Map())

  /** @type {import('vue').Ref<boolean>} Whether polling is active */
  const isPolling = ref(false)

  /** @type {import('vue').Ref<string|null>} Last error message */
  const error = ref(null)

  /** @type {AbortController|null} Controller for canceling ongoing requests */
  let abortController = null

  /**
   * US-018: Callback function for when jobs complete or fail.
   * Set this to receive notifications when job status transitions happen.
   * @type {((completedReceiptIds: number[], failedJobIds: string[]) => void)|null}
   */
  let onJobsTransitionCallback = null

  // ============ Computed ============

  /** Number of jobs in 'pending' status */
  const pendingCount = computed(() => {
    let count = 0
    for (const job of jobs.value.values()) {
      if (job.status === 'pending') count++
    }
    return count
  })

  /** Number of jobs in 'processing' status */
  const processingCount = computed(() => {
    let count = 0
    for (const job of jobs.value.values()) {
      if (job.status === 'processing') count++
    }
    return count
  })

  /** Number of jobs in 'completed' status */
  const completedCount = computed(() => {
    let count = 0
    for (const job of jobs.value.values()) {
      if (job.status === 'completed') count++
    }
    return count
  })

  /** Number of jobs in 'failed' status */
  const failedCount = computed(() => {
    let count = 0
    for (const job of jobs.value.values()) {
      if (job.status === 'failed') count++
    }
    return count
  })

  /** Total number of jobs */
  const totalCount = computed(() => jobs.value.size)

  /** Progress percentage (0-100) */
  const progress = computed(() => {
    if (totalCount.value === 0) return 0
    const finished = completedCount.value + failedCount.value
    return Math.round((finished / totalCount.value) * 100)
  })

  /** Whether all jobs are finished (completed or failed) */
  const isComplete = computed(() => {
    if (totalCount.value === 0) return false
    return pendingCount.value === 0 && processingCount.value === 0
  })

  /** Total amount from all completed receipts (from backend response) */
  const totalAmount = ref(0)

  /** Jobs as array for iteration */
  const jobsArray = computed(() => Array.from(jobs.value.values()))

  // ============ Actions ============

  /**
   * Start long-polling for batch status updates.
   * Uses 30-second wait parameter for efficient long-polling.
   *
   * @param {string} id - Batch ID to track
   * @param {Array<{job_id: string, filename: string}>} [initialJobs] - Optional initial jobs to populate immediately
   */
  async function startPolling(id, initialJobs = null) {
    // Stop any existing polling
    stopPolling()

    // Reset state
    batchId.value = id
    jobs.value = new Map()
    error.value = null
    isPolling.value = true
    totalAmount.value = 0

    // US-017: Pre-populate jobs immediately if initial jobs provided
    // This ensures jobs appear instantly in the table before polling fetches real status
    if (initialJobs && Array.isArray(initialJobs)) {
      for (const job of initialJobs) {
        jobs.value.set(job.job_id, {
          job_id: job.job_id,
          filename: job.filename,
          status: 'pending', // Initial status is always pending
          receipt_id: null,
          error_message: null,
          confidence: null
        })
      }
    }

    // US-009: Persist batch ID to localStorage for resume after refresh
    addActiveBatch(id)

    // Create new abort controller for this polling session
    abortController = new AbortController()

    // Start polling loop
    await pollLoop()
  }

  /**
   * Stop polling and cancel any pending requests.
   */
  function stopPolling() {
    isPolling.value = false

    // Cancel any pending request
    if (abortController) {
      abortController.abort()
      abortController = null
    }
  }

  /**
   * Internal polling loop - fetches status until complete or stopped.
   */
  async function pollLoop() {
    while (isPolling.value && batchId.value !== null) {
      try {
        // Fetch batch status with 30-second wait (long-polling)
        const response = await api.get(`/bulk/batches/${batchId.value}/status`, {
          params: { wait: 30 },
          signal: abortController?.signal,
          // Extend timeout for long-polling
          timeout: 35000
        })

        const data = response.data

        // US-018: Track status transitions for callback notification
        const previousJobs = jobs.value
        const newlyCompletedReceiptIds = []
        const newlyFailedJobIds = []

        // Update jobs map
        const newJobs = new Map()
        for (const job of data.jobs) {
          const previousJob = previousJobs.get(job.job_id)
          const previousStatus = previousJob?.status

          // Check for transitions to completed/failed
          if (previousStatus && previousStatus !== job.status) {
            if (job.status === 'completed' && job.receipt_id) {
              newlyCompletedReceiptIds.push(job.receipt_id)
              console.log(`[BatchProgress] Job ${job.job_id} completed -> receipt ${job.receipt_id}`)
            } else if (job.status === 'failed') {
              newlyFailedJobIds.push(job.job_id)
              console.log(`[BatchProgress] Job ${job.job_id} failed: ${job.error_message}`)
            }
          }

          newJobs.set(job.job_id, {
            job_id: job.job_id,
            filename: job.filename,
            status: job.status,
            receipt_id: job.receipt_id || null,
            error_message: job.error_message || null,
            confidence: job.confidence || null
          })
        }
        jobs.value = newJobs

        // US-018: Notify listener of status transitions
        if (onJobsTransitionCallback && (newlyCompletedReceiptIds.length > 0 || newlyFailedJobIds.length > 0)) {
          onJobsTransitionCallback(newlyCompletedReceiptIds, newlyFailedJobIds)
        }

        // Update total amount from backend
        totalAmount.value = data.total_amount || 0

        // Clear any previous error
        error.value = null

        // Check if all jobs are finished
        const finished = data.completed_count + data.failed_count
        if (finished >= data.total_files) {
          console.log('[BatchProgress] All jobs finished, stopping polling')
          isPolling.value = false

          // US-009: Remove completed batch from localStorage
          if (batchId.value) {
            removeActiveBatch(batchId.value)
          }

          break
        }

      } catch (err) {
        // Ignore abort errors (expected when stopPolling is called)
        if (err.name === 'AbortError' || err.code === 'ERR_CANCELED') {
          console.log('[BatchProgress] Polling aborted')
          break
        }

        // Handle other errors
        console.error('[BatchProgress] Polling error:', err)
        error.value = err.response?.data?.detail || err.message || 'Failed to fetch batch status'

        // Wait before retrying on error
        await sleep(2000)
      }
    }
  }

  /**
   * Reset store to initial state.
   */
  function reset() {
    stopPolling()
    batchId.value = null
    jobs.value = new Map()
    error.value = null
    totalAmount.value = 0
  }

  // ============ US-009: Auto-Resume Functions ============

  /**
   * Get list of active batch IDs stored in localStorage.
   * Used by views to check if there are batches to resume after refresh.
   *
   * @returns {string[]} Array of stored batch IDs
   */
  function getStoredBatchIds() {
    return getStoredActiveBatches()
  }

  /**
   * Clear a specific batch ID from localStorage.
   * Called when a batch is determined to be complete (all items processed).
   *
   * @param {string} batchIdToRemove - Batch ID to remove from storage
   */
  function clearStoredBatch(batchIdToRemove) {
    removeActiveBatch(batchIdToRemove)
  }

  /**
   * Clear all stored batch IDs from localStorage.
   * Used during cleanup or when all batches are confirmed complete.
   */
  function clearAllStoredBatches() {
    saveActiveBatches([])
  }

  /**
   * Helper: Sleep for given milliseconds.
   * @param {number} ms - Milliseconds to sleep
   */
  function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
  }

  // ============ US-023: Restore Jobs from Batch ============

  /**
   * Restore jobs from a stored batch by fetching current status from API.
   * Only pending/processing jobs are added to the store (completed/failed are already receipts).
   *
   * US-023: Called on page refresh/return to restore visibility of active jobs.
   *
   * @param {string} storedBatchId - The batch ID to restore from
   * @returns {Promise<{hasActiveJobs: boolean, jobCount: number}>} Result of restoration
   */
  async function restoreJobsFromBatch(storedBatchId) {
    try {
      console.log(`[BatchProgress] Restoring jobs from batch ${storedBatchId}`)

      // Fetch current batch status from API (no wait, just get current state)
      const response = await api.get(`/bulk/batches/${storedBatchId}/status`, {
        params: { wait: 0 },
        timeout: 10000
      })

      const data = response.data

      if (!data.jobs || data.jobs.length === 0) {
        console.log(`[BatchProgress] Batch ${storedBatchId} has no jobs, removing from storage`)
        removeActiveBatch(storedBatchId)
        return { hasActiveJobs: false, jobCount: 0 }
      }

      // Count and filter active jobs (pending/processing only)
      const activeJobs = data.jobs.filter(
        job => job.status === 'pending' || job.status === 'processing'
      )

      if (activeJobs.length === 0) {
        // All jobs are completed or failed - no need to restore to UI
        console.log(`[BatchProgress] Batch ${storedBatchId} has no active jobs (all completed/failed), removing from storage`)
        removeActiveBatch(storedBatchId)
        return { hasActiveJobs: false, jobCount: 0 }
      }

      // Set batch ID and add active jobs to store
      batchId.value = storedBatchId

      // Add jobs to the Map (merge with existing if any)
      for (const job of activeJobs) {
        jobs.value.set(job.job_id, {
          job_id: job.job_id,
          filename: job.filename,
          status: job.status,
          receipt_id: job.receipt_id || null,
          error_message: job.error_message || null,
          confidence: job.confidence || null
        })
      }

      console.log(`[BatchProgress] Restored ${activeJobs.length} active jobs from batch ${storedBatchId}`)

      // Start polling for updates
      if (!isPolling.value) {
        isPolling.value = true
        abortController = new AbortController()
        // Start polling loop in background
        pollLoop()
      }

      return { hasActiveJobs: true, jobCount: activeJobs.length }
    } catch (err) {
      console.error(`[BatchProgress] Error restoring batch ${storedBatchId}:`, err)

      // If batch not found (404), remove it from storage
      if (err.response?.status === 404) {
        console.log(`[BatchProgress] Batch ${storedBatchId} not found, removing from storage`)
        removeActiveBatch(storedBatchId)
      }

      return { hasActiveJobs: false, jobCount: 0 }
    }
  }

  // ============ US-016: Cancel Actions ============

  /**
   * Cancel a specific job by job ID.
   * After successful cancellation, the job is removed from the jobs Map.
   *
   * @param {string} jobId - The job ID to cancel
   * @returns {Promise<{success: boolean, message: string}>} Result of the operation
   */
  async function cancelJob(jobId) {
    try {
      const response = await api.post(`/bulk/cancel/${jobId}`)
      const data = response.data

      if (data.success) {
        // Remove the cancelled job from the Map
        jobs.value.delete(jobId)

        console.log(`[BatchProgress] Job ${jobId} cancelled successfully`)
        return {
          success: true,
          message: data.message || 'Job anulat cu succes'
        }
      }

      return {
        success: false,
        message: data.message || 'Eroare la anularea job-ului'
      }
    } catch (err) {
      console.error('[BatchProgress] Error cancelling job:', err)

      // Extract error message from response or use generic message
      const errorMessage = err.response?.data?.detail ||
                           err.response?.data?.message ||
                           err.message ||
                           'Eroare la anularea job-ului'

      return {
        success: false,
        message: errorMessage
      }
    }
  }

  /**
   * Cancel all pending/processing jobs in a batch.
   * After successful cancellation, cancelled jobs are removed from the jobs Map.
   * Completed/failed jobs remain in the Map for visibility.
   *
   * @param {number|string} batchIdToCancel - The batch ID to cancel
   * @returns {Promise<{success: boolean, message: string, cancelledCount: number, skippedCount: number}>}
   */
  async function cancelBatch(batchIdToCancel) {
    try {
      const response = await api.post(`/bulk/cancel-batch/${batchIdToCancel}`)
      const data = response.data

      if (data.success) {
        // Remove all cancelled jobs from the Map
        // Jobs with 'pending' or 'processing' status were cancelled
        const jobsToRemove = []
        for (const [jobId, job] of jobs.value.entries()) {
          if (job.status === 'pending' || job.status === 'processing') {
            jobsToRemove.push(jobId)
          }
        }

        // Remove the jobs
        for (const jobId of jobsToRemove) {
          jobs.value.delete(jobId)
        }

        console.log(`[BatchProgress] Batch ${batchIdToCancel} cancelled: ${data.cancelled_count} cancelled, ${data.skipped_count} skipped`)

        return {
          success: true,
          message: data.message || `${data.cancelled_count} job-uri anulate`,
          cancelledCount: data.cancelled_count,
          skippedCount: data.skipped_count
        }
      }

      return {
        success: false,
        message: data.message || 'Eroare la anularea batch-ului',
        cancelledCount: 0,
        skippedCount: 0
      }
    } catch (err) {
      console.error('[BatchProgress] Error cancelling batch:', err)

      // Extract error message from response or use generic message
      const errorMessage = err.response?.data?.detail ||
                           err.response?.data?.message ||
                           err.message ||
                           'Eroare la anularea batch-ului'

      return {
        success: false,
        message: errorMessage,
        cancelledCount: 0,
        skippedCount: 0
      }
    }
  }

  // ============ Return Public API ============

  return {
    // State
    batchId,
    jobs,
    isPolling,
    error,

    // Computed
    pendingCount,
    processingCount,
    completedCount,
    failedCount,
    totalCount,
    progress,
    isComplete,
    totalAmount,
    jobsArray,

    // Actions
    startPolling,
    stopPolling,
    reset,

    // US-009: Auto-Resume Functions
    getStoredBatchIds,
    clearStoredBatch,
    clearAllStoredBatches,

    // US-023: Restore Jobs from Batch
    restoreJobsFromBatch,

    // US-016: Cancel Actions
    cancelJob,
    cancelBatch,

    // US-018: Transition Callback
    setOnJobsTransitionCallback: (callback) => {
      onJobsTransitionCallback = callback
    }
  }
})
