/**
 * Document Proxy Queue
 *
 * Manages proxy documents that represent PDFs detected by the extension.
 * Each proxy is identified by filename + content hash (MD5).
 * Proxies can be "live" (visible by default) or "archived".
 *
 * Uses chrome.storage.local for persistence across service worker restarts.
 */

import { DetectedPDF } from './pdf-detector';

const STORAGE_KEY = 'documentProxies';
const MAX_ENTRIES = 100;
const ARCHIVED_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000; // 30 days

/**
 * Binect document status (server-side state)
 */
export type BinectStatus =
  | 'pending'       // Not yet uploaded to Binect
  | 'uploading'     // Upload in progress
  | 'failed'        // Upload failed
  | 'in_basket'     // Uploaded, SHIPPABLE, awaiting order
  | 'ordering'      // Order in progress
  | 'in_production' // PRODUCTION_QUEUE or PRINTING
  | 'sent'          // SENT - terminal
  | 'canceled';     // CANCELED - terminal

// Keep PDFStatus as alias for backward compatibility
export type PDFStatus = BinectStatus;

/**
 * Document Proxy - local representation of a PDF
 *
 * Identified by filename + contentHash for deduplication.
 * The archived flag controls visibility (live vs archived view).
 */
export interface DocumentProxy extends DetectedPDF {
  // Identification
  contentHash?: string;           // MD5 hash of content (set after upload)

  // Local state
  archived: boolean;              // If true, shown in archive view instead of live

  // Binect state (server-side)
  binectStatus: BinectStatus;     // Current status with Binect
  binectDocumentId?: number;      // Document ID on Binect server
  binectStatusCode?: number;      // Raw status code from Binect (1-7)
  binectStatusText?: string;      // Human-readable status from Binect

  // Document details
  price?: number;                 // Price in euro cents
  recipientAddress?: string;      // Extracted recipient address
  errorMessage?: string;          // Error message if failed

  // Timestamps
  uploadedAt?: number;            // When uploaded to Binect
  orderedAt?: number;             // When order was placed
}

// Keep PDFQueueEntry as alias for backward compatibility
export type PDFQueueEntry = DocumentProxy;

/**
 * Metadata for status updates
 */
export interface PDFStatusMeta {
  binectDocumentId?: number;
  binectStatus?: number;
  binectStatusText?: string;
  price?: number;
  recipientAddress?: string;
  errorMessage?: string;
  contentHash?: string;
}

interface ProxyQueueState {
  entries: DocumentProxy[];
  lastUpdated: number;
}

/**
 * Load queue from storage
 */
export async function loadQueue(): Promise<ProxyQueueState> {
  const result = await chrome.storage.local.get(STORAGE_KEY);
  if (result[STORAGE_KEY]) {
    // Migrate old entries that don't have archived field
    const state = result[STORAGE_KEY] as ProxyQueueState;
    for (const entry of state.entries) {
      if (entry.archived === undefined) {
        // Migrate: dismissed becomes archived, others are live
        entry.archived = (entry as unknown as { status: string }).status === 'dismissed';
      }
      // Migrate: old 'status' field to 'binectStatus'
      if (!entry.binectStatus && (entry as unknown as { status: string }).status) {
        const oldStatus = (entry as unknown as { status: string }).status;
        if (oldStatus !== 'dismissed') {
          entry.binectStatus = oldStatus as BinectStatus;
        } else {
          entry.binectStatus = 'pending';
        }
      }
    }
    return state;
  }
  return { entries: [], lastUpdated: Date.now() };
}

/**
 * Save queue to storage
 */
export async function saveQueue(state: ProxyQueueState): Promise<void> {
  state.lastUpdated = Date.now();
  await chrome.storage.local.set({ [STORAGE_KEY]: state });
}

/**
 * Find existing proxy by filename and content hash
 * If contentHash is not provided, matches by filename only (for pre-upload detection)
 */
function findExistingProxy(
  entries: DocumentProxy[],
  filename: string,
  contentHash?: string
): DocumentProxy | undefined {
  if (contentHash) {
    // Exact match: filename + hash
    return entries.find(e => e.filename === filename && e.contentHash === contentHash);
  }
  // For pre-upload: check by filename and URL (same source)
  return undefined; // Don't match without hash - let it be added
}

/**
 * Find existing proxy by Binect document ID
 */
function findProxyByBinectId(
  entries: DocumentProxy[],
  binectDocumentId: number
): DocumentProxy | undefined {
  return entries.find(e => e.binectDocumentId === binectDocumentId);
}

/**
 * Add a PDF to the queue (creates a new proxy document)
 *
 * Returns the created entry, or existing entry if duplicate found.
 * Duplicates are identified by filename + contentHash.
 */
export async function addPDF(
  pdf: DetectedPDF,
  contentHash?: string
): Promise<DocumentProxy | null> {
  const state = await loadQueue();

  // Check for duplicate by filename + hash (if hash provided)
  if (contentHash) {
    const existing = findExistingProxy(state.entries, pdf.filename, contentHash);
    if (existing) {
      console.log('[Proxy Queue] Duplicate found by hash, returning existing:', pdf.filename);
      // If it was archived, restore it to live
      if (existing.archived) {
        existing.archived = false;
        await saveQueue(state);
      }
      return existing;
    }
  }

  // Check for existing entry with same URL (same source, not yet hashed)
  const byUrl = state.entries.find(e => e.url === pdf.url && !e.contentHash);
  if (byUrl) {
    console.log('[Proxy Queue] Found existing by URL:', pdf.filename);
    return byUrl;
  }

  // Create new proxy document
  const proxy: DocumentProxy = {
    ...pdf,
    contentHash,
    archived: false,
    binectStatus: 'pending'
  };

  // Add to beginning (most recent first)
  state.entries.unshift(proxy);

  // Enforce max entries
  await enforceMaxEntries(state);

  await saveQueue(state);
  console.log('[Proxy Queue] Created new proxy:', pdf.filename);
  return proxy;
}

/**
 * Create or update a proxy from server document
 * Used when syncing with Binect server
 */
export async function syncFromServer(
  binectDocumentId: number,
  filename: string,
  binectStatusCode: number,
  binectStatusText: string,
  price?: number,
  recipientAddress?: string,
  errorMessage?: string
): Promise<DocumentProxy> {
  const state = await loadQueue();

  // Check if we already have a proxy for this Binect document
  let proxy = findProxyByBinectId(state.entries, binectDocumentId);

  if (proxy) {
    // Update existing proxy
    proxy.binectStatusCode = binectStatusCode;
    proxy.binectStatusText = binectStatusText;
    proxy.binectStatus = mapBinectStatusCode(binectStatusCode);
    if (price !== undefined) proxy.price = price;
    if (recipientAddress) proxy.recipientAddress = recipientAddress;
    if (errorMessage) proxy.errorMessage = errorMessage;
  } else {
    // Create new proxy from server data
    proxy = {
      id: `server-${binectDocumentId}`,
      filename,
      url: '',
      size: 0,
      timestamp: Date.now(),
      sourceDomain: 'binect.de',
      archived: false,
      binectStatus: mapBinectStatusCode(binectStatusCode),
      binectDocumentId,
      binectStatusCode,
      binectStatusText,
      price,
      recipientAddress,
      errorMessage
    };
    state.entries.unshift(proxy);
  }

  await saveQueue(state);
  return proxy;
}

/**
 * Map Binect status code to BinectStatus
 */
function mapBinectStatusCode(code: number): BinectStatus {
  switch (code) {
    case 1: return 'pending'; // IN_PREPARATION
    case 2: return 'in_basket'; // SHIPPABLE
    case 3: return 'in_production'; // PRODUCTION_QUEUE
    case 4: return 'in_production'; // PRINTING
    case 5: return 'sent'; // SENT
    case 6: return 'canceled'; // CANCELED
    case 7: return 'failed'; // ERRONEOUS
    default: return 'pending';
  }
}

/**
 * Update the status of a proxy document
 */
export async function updatePDFStatus(
  id: string,
  status: BinectStatus,
  meta?: PDFStatusMeta
): Promise<void> {
  const state = await loadQueue();
  const entry = state.entries.find(e => e.id === id);

  if (!entry) {
    console.warn('[Proxy Queue] Proxy not found for status update:', id);
    return;
  }

  entry.binectStatus = status;

  // Update metadata
  if (meta?.binectDocumentId !== undefined) {
    entry.binectDocumentId = meta.binectDocumentId;
  }
  if (meta?.binectStatus !== undefined) {
    entry.binectStatusCode = meta.binectStatus;
  }
  if (meta?.binectStatusText !== undefined) {
    entry.binectStatusText = meta.binectStatusText;
  }
  if (meta?.price !== undefined) {
    entry.price = meta.price;
  }
  if (meta?.recipientAddress !== undefined) {
    entry.recipientAddress = meta.recipientAddress;
  }
  if (meta?.errorMessage !== undefined) {
    entry.errorMessage = meta.errorMessage;
  }
  if (meta?.contentHash !== undefined) {
    entry.contentHash = meta.contentHash;
  }

  // Clear error message when transitioning to non-erroneous state
  if (status !== 'failed' && entry.errorMessage) {
    entry.errorMessage = undefined;
  }

  // Set timestamps based on status
  if (status === 'in_basket' && !entry.uploadedAt) {
    entry.uploadedAt = Date.now();
  }
  if (status === 'in_production' && !entry.orderedAt) {
    entry.orderedAt = Date.now();
  }

  await saveQueue(state);
  console.log('[Proxy Queue] Updated status:', id, status);
}

/**
 * Archive a proxy document (move from live to archive)
 */
export async function archiveProxy(id: string): Promise<void> {
  const state = await loadQueue();
  const entry = state.entries.find(e => e.id === id);

  if (!entry) {
    console.warn('[Proxy Queue] Proxy not found for archiving:', id);
    return;
  }

  entry.archived = true;
  await saveQueue(state);
  console.log('[Proxy Queue] Archived proxy:', id);
}

/**
 * Clear server-side fields from a proxy (when deleted from server)
 * This makes the proxy "local only" again
 */
export async function clearServerFields(id: string): Promise<void> {
  const state = await loadQueue();
  const entry = state.entries.find(e => e.id === id);

  if (!entry) {
    console.warn('[Proxy Queue] Proxy not found for clearing server fields:', id);
    return;
  }

  // Clear all server-related fields
  entry.binectDocumentId = undefined;
  entry.binectStatusCode = undefined;
  entry.binectStatusText = undefined;
  entry.binectStatus = 'pending'; // Reset to pending since it's no longer on server
  entry.price = undefined;
  entry.recipientAddress = undefined;
  entry.errorMessage = undefined;
  entry.uploadedAt = undefined;
  entry.orderedAt = undefined;

  await saveQueue(state);
  console.log('[Proxy Queue] Cleared server fields for proxy:', id);
}

/**
 * Attach server document to a proxy
 * Used when re-linking a local proxy to a server document
 */
export async function attachServerDocument(
  id: string,
  binectDocumentId: number,
  binectStatusCode: number,
  binectStatusText: string,
  price?: number,
  recipientAddress?: string,
  errorMessage?: string
): Promise<void> {
  const state = await loadQueue();
  const entry = state.entries.find(e => e.id === id);

  if (!entry) {
    console.warn('[Proxy Queue] Proxy not found for attaching server document:', id);
    return;
  }

  entry.binectDocumentId = binectDocumentId;
  entry.binectStatusCode = binectStatusCode;
  entry.binectStatusText = binectStatusText;
  entry.binectStatus = mapBinectStatusCode(binectStatusCode);
  if (price !== undefined) entry.price = price;
  if (recipientAddress) entry.recipientAddress = recipientAddress;
  if (errorMessage) entry.errorMessage = errorMessage;

  await saveQueue(state);
  console.log('[Proxy Queue] Attached server document', binectDocumentId, 'to proxy:', id);
}

/**
 * Restore a proxy document (move from archive to live)
 */
export async function restoreProxy(id: string): Promise<void> {
  const state = await loadQueue();
  const entry = state.entries.find(e => e.id === id);

  if (!entry) {
    console.warn('[Proxy Queue] Proxy not found for restoring:', id);
    return;
  }

  entry.archived = false;
  await saveQueue(state);
  console.log('[Proxy Queue] Restored proxy:', id);
}

/**
 * Remove a proxy document completely
 */
export async function removePDF(id: string): Promise<void> {
  const state = await loadQueue();
  const index = state.entries.findIndex(e => e.id === id);

  if (index === -1) {
    console.warn('[Proxy Queue] Proxy not found for removal:', id);
    return;
  }

  state.entries.splice(index, 1);
  await saveQueue(state);
  console.log('[Proxy Queue] Removed proxy:', id);
}

// Keep dismissPDF as alias for archiveProxy (backward compatibility)
export const dismissPDF = archiveProxy;

/**
 * Get live proxy documents (not archived)
 */
export async function getLiveProxies(): Promise<DocumentProxy[]> {
  const state = await loadQueue();
  return state.entries.filter(e => !e.archived);
}

/**
 * Get archived proxy documents
 */
export async function getArchivedProxies(): Promise<DocumentProxy[]> {
  const state = await loadQueue();
  return state.entries.filter(e => e.archived);
}

/**
 * Get all proxy documents (live and archived)
 */
export async function getAllPDFs(): Promise<DocumentProxy[]> {
  const state = await loadQueue();
  return state.entries;
}

/**
 * Get proxies that need user action (pending, failed, in_basket) - live only
 */
export async function getActionablePDFs(): Promise<DocumentProxy[]> {
  const state = await loadQueue();
  return state.entries.filter(e =>
    !e.archived && (
      e.binectStatus === 'pending' ||
      e.binectStatus === 'failed' ||
      e.binectStatus === 'in_basket'
    )
  );
}

/**
 * Get count of PDFs needing action (for badge)
 */
export async function getActionableCount(): Promise<number> {
  const actionable = await getActionablePDFs();
  return actionable.length;
}

/**
 * Get all Binect document IDs that we're tracking
 */
export async function getTrackedBinectIds(): Promise<number[]> {
  const state = await loadQueue();
  return state.entries
    .filter(e => e.binectDocumentId !== undefined)
    .map(e => e.binectDocumentId!);
}

// Legacy aliases
export const getPendingPDFs = getActionablePDFs;
export const getPendingCount = getActionableCount;

/**
 * Clean up old entries to prevent unbounded growth
 */
export async function cleanupOldEntries(): Promise<void> {
  const state = await loadQueue();
  const now = Date.now();
  const initialCount = state.entries.length;

  state.entries = state.entries.filter(entry => {
    // Always keep live entries that are active
    if (!entry.archived && (
      entry.binectStatus === 'pending' ||
      entry.binectStatus === 'uploading' ||
      entry.binectStatus === 'in_basket' ||
      entry.binectStatus === 'ordering' ||
      entry.binectStatus === 'in_production'
    )) {
      return true;
    }

    // Remove old archived entries
    if (entry.archived) {
      const age = now - (entry.orderedAt || entry.uploadedAt || entry.timestamp);
      if (age > ARCHIVED_MAX_AGE_MS) {
        return false;
      }
    }

    return true;
  });

  if (state.entries.length < initialCount) {
    await saveQueue(state);
    console.log('[Proxy Queue] Cleaned up', initialCount - state.entries.length, 'old entries');
  }
}

/**
 * Enforce maximum entries by removing oldest archived entries
 */
async function enforceMaxEntries(state: ProxyQueueState): Promise<void> {
  while (state.entries.length > MAX_ENTRIES) {
    let removeIndex = -1;
    let oldestTime = Infinity;

    // Find oldest archived entry first
    for (let i = state.entries.length - 1; i >= 0; i--) {
      const entry = state.entries[i];
      if (entry.archived) {
        const entryTime = entry.orderedAt || entry.uploadedAt || entry.timestamp;
        if (entryTime < oldestTime) {
          oldestTime = entryTime;
          removeIndex = i;
        }
      }
    }

    // If no archived entries, find oldest terminal live entry
    if (removeIndex === -1) {
      const terminalStatuses: BinectStatus[] = ['sent', 'canceled'];
      for (let i = state.entries.length - 1; i >= 0; i--) {
        const entry = state.entries[i];
        if (terminalStatuses.includes(entry.binectStatus)) {
          const entryTime = entry.orderedAt || entry.uploadedAt || entry.timestamp;
          if (entryTime < oldestTime) {
            oldestTime = entryTime;
            removeIndex = i;
          }
        }
      }
    }

    // If still nothing to remove, we can't shrink
    if (removeIndex === -1) {
      console.warn('[Proxy Queue] Max entries reached, but all are active');
      break;
    }

    state.entries.splice(removeIndex, 1);
  }
}
