/**
 * Idempotency Key Lifecycle Management
 * 
 * Tracks idempotency keys through their lifecycle:
 * - pending: Key generated, request not yet sent
 * - processing: Request in progress
 * - completed: Request succeeded
 * - failed: Request failed
 * - expired: Key expired (24h window)
 * 
 * Part of Sacred Pipeline Phase 4: Transaction Resilience
 */

import { openDB, DBSchema, IDBPDatabase } from 'idb';
import { logger } from './logger';

export type IdempotencyStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'expired';

export interface IdempotencyRecord {
  key: string;
  action: 'approval' | 'rejection' | 'retry';
  submissionId: string;
  itemIds: string[];
  userId: string;
  status: IdempotencyStatus;
  createdAt: number;
  updatedAt: number;
  expiresAt: number;
  attempts: number;
  lastError?: string;
  completedAt?: number;
}

interface IdempotencyDB extends DBSchema {
  idempotency_keys: {
    key: string;
    value: IdempotencyRecord;
    indexes: {
      'by-submission': string;
      'by-status': IdempotencyStatus;
      'by-expiry': number;
    };
  };
}

const DB_NAME = 'thrillwiki-idempotency';
const DB_VERSION = 1;
const STORE_NAME = 'idempotency_keys';
const KEY_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours

let dbInstance: IDBPDatabase<IdempotencyDB> | null = null;

async function getDB(): Promise<IDBPDatabase<IdempotencyDB>> {
  if (dbInstance) return dbInstance;

  dbInstance = await openDB<IdempotencyDB>(DB_NAME, DB_VERSION, {
    upgrade(db) {
      if (!db.objectStoreNames.contains(STORE_NAME)) {
        const store = db.createObjectStore(STORE_NAME, { keyPath: 'key' });
        store.createIndex('by-submission', 'submissionId');
        store.createIndex('by-status', 'status');
        store.createIndex('by-expiry', 'expiresAt');
      }
    },
  });

  return dbInstance;
}

/**
 * Register a new idempotency key
 */
export async function registerIdempotencyKey(
  key: string,
  action: IdempotencyRecord['action'],
  submissionId: string,
  itemIds: string[],
  userId: string
): Promise<IdempotencyRecord> {
  const db = await getDB();
  const now = Date.now();

  const record: IdempotencyRecord = {
    key,
    action,
    submissionId,
    itemIds,
    userId,
    status: 'pending',
    createdAt: now,
    updatedAt: now,
    expiresAt: now + KEY_TTL_MS,
    attempts: 0,
  };

  await db.add(STORE_NAME, record);

  logger.info('[IdempotencyLifecycle] Registered key', {
    key,
    action,
    submissionId,
    itemCount: itemIds.length,
  });

  return record;
}

/**
 * Update idempotency key status
 */
export async function updateIdempotencyStatus(
  key: string,
  status: IdempotencyStatus,
  error?: string
): Promise<void> {
  const db = await getDB();
  const record = await db.get(STORE_NAME, key);

  if (!record) {
    logger.warn('[IdempotencyLifecycle] Key not found for update', { key, status });
    return;
  }

  const now = Date.now();
  record.status = status;
  record.updatedAt = now;

  if (status === 'processing') {
    record.attempts += 1;
  }

  if (status === 'completed') {
    record.completedAt = now;
  }

  if (status === 'failed' && error) {
    record.lastError = error;
  }

  await db.put(STORE_NAME, record);

  logger.info('[IdempotencyLifecycle] Updated key status', {
    key,
    status,
    attempts: record.attempts,
  });
}

/**
 * Get idempotency record by key
 */
export async function getIdempotencyRecord(key: string): Promise<IdempotencyRecord | null> {
  const db = await getDB();
  const record = await db.get(STORE_NAME, key);

  // Check if expired
  if (record && record.expiresAt < Date.now()) {
    await updateIdempotencyStatus(key, 'expired');
    return { ...record, status: 'expired' };
  }

  return record || null;
}

/**
 * Check if key exists and is valid
 */
export async function isIdempotencyKeyValid(key: string): Promise<boolean> {
  const record = await getIdempotencyRecord(key);
  
  if (!record) return false;
  if (record.status === 'expired') return false;
  if (record.expiresAt < Date.now()) return false;

  return true;
}

/**
 * Get all keys for a submission
 */
export async function getSubmissionIdempotencyKeys(
  submissionId: string
): Promise<IdempotencyRecord[]> {
  const db = await getDB();
  const index = db.transaction(STORE_NAME).store.index('by-submission');
  return await index.getAll(submissionId);
}

/**
 * Get keys by status
 */
export async function getIdempotencyKeysByStatus(
  status: IdempotencyStatus
): Promise<IdempotencyRecord[]> {
  const db = await getDB();
  const index = db.transaction(STORE_NAME).store.index('by-status');
  return await index.getAll(status);
}

/**
 * Clean up expired keys
 */
export async function cleanupExpiredKeys(): Promise<number> {
  const db = await getDB();
  const now = Date.now();
  const tx = db.transaction(STORE_NAME, 'readwrite');
  const index = tx.store.index('by-expiry');

  let deletedCount = 0;

  // Get all expired keys
  for await (const cursor of index.iterate()) {
    if (cursor.value.expiresAt < now) {
      await cursor.delete();
      deletedCount++;
    }
  }

  await tx.done;

  if (deletedCount > 0) {
    logger.info('[IdempotencyLifecycle] Cleaned up expired keys', { deletedCount });
  }

  return deletedCount;
}

/**
 * Get idempotency statistics
 */
export async function getIdempotencyStats(): Promise<{
  total: number;
  pending: number;
  processing: number;
  completed: number;
  failed: number;
  expired: number;
}> {
  const db = await getDB();
  const all = await db.getAll(STORE_NAME);
  const now = Date.now();

  const stats = {
    total: all.length,
    pending: 0,
    processing: 0,
    completed: 0,
    failed: 0,
    expired: 0,
  };

  all.forEach(record => {
    // Mark as expired if TTL passed
    if (record.expiresAt < now) {
      stats.expired++;
    } else {
      stats[record.status]++;
    }
  });

  return stats;
}

/**
 * Auto-cleanup: Run periodically to remove expired keys
 */
export function startAutoCleanup(intervalMinutes: number = 60): () => void {
  const intervalId = setInterval(async () => {
    try {
      await cleanupExpiredKeys();
    } catch (error) {
      logger.error('[IdempotencyLifecycle] Auto-cleanup failed', { error });
    }
  }, intervalMinutes * 60 * 1000);

  // Run immediately on start
  cleanupExpiredKeys();

  // Return cleanup function
  return () => clearInterval(intervalId);
}
