import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
import { db } from '../lib/db';
import { SyncManager } from './sync-manager';

describe('Story 3.3: SyncManager Service', () => {
  beforeEach(async () => {
    // Clear all tables before each test
    await db.syncQueue.clear();
    await db.drafts.clear();
    await db.chatLogs.clear();
  });

  afterEach(() => {
    vi.restoreAllMocks();
  });

  describe('queueAction', () => {
    it('should add an action to the sync queue', async () => {
      const payload = {
        draftData: {
          sessionId: 'test-session',
          title: 'Test Draft',
          content: 'Test content',
          tags: ['test'],
          createdAt: Date.now(),
          status: 'draft' as const,
        },
      };

      const id = await SyncManager.queueAction('saveDraft', payload);
      expect(id).toBeDefined();

      const items = await db.syncQueue.toArray();
      expect(items).toHaveLength(1);
      expect(items[0]).toMatchObject({
        action: 'saveDraft',
        payload,
        status: 'pending',
        retries: 0,
      });
    });

    it('should support all action types', async () => {
      const saveId = await SyncManager.queueAction('saveDraft', {
        draftData: {
          sessionId: 'test',
          title: 'Test',
          content: 'Test',
          tags: [],
          createdAt: Date.now(),
          status: 'draft' as const,
        },
      });

      const deleteId = await SyncManager.queueAction('deleteDraft', {
        draftId: 123,
      });

      const completeId = await SyncManager.queueAction('completeDraft', {
        draftId: 456,
      });

      expect(saveId).toBeDefined();
      expect(deleteId).toBeDefined();
      expect(completeId).toBeDefined();

      const items = await db.syncQueue.toArray();
      expect(items).toHaveLength(3);
    });
  });

  describe('processQueue', () => {
    it('should process saveDraft action', async () => {
      // Arrange
      const payload = {
        draftData: {
          sessionId: 'test-session',
          title: 'Test Draft',
          content: 'Test content',
          tags: ['test'],
          createdAt: Date.now(),
          status: 'draft' as const,
        },
      };
      const id = await SyncManager.queueAction('saveDraft', payload);
      expect(id).toBeDefined();

      const items = await db.syncQueue.toArray();
      expect(items).toHaveLength(1);
      // Act
      await SyncManager.processQueue();

      // Assert
      // MVP Change: We no longer call DraftService methods to avoid duplication
      // Instead we verify that the item was processed and removed from queue
      const processedItems = await db.syncQueue.toArray();
      expect(processedItems).toHaveLength(0);

      // Draft should NOT be created by SyncManager (it's created by Service before queuing)
      const drafts = await db.drafts.toArray();
      expect(drafts).toHaveLength(0);
    });

    it('should process deleteDraft action', async () => {
      // Arrange
      const now = Date.now();
      const draftId = await db.drafts.add({
        sessionId: 's1',
        title: 'Draft 1',
        content: 'Content 1',
        tags: [],
        createdAt: now,
        status: 'draft',
      });
      await SyncManager.queueAction('deleteDraft', { draftId });

      // Act
      await SyncManager.processQueue();

      // Assert
      // Assert
      const items = await db.syncQueue.toArray();
      expect(items).toHaveLength(0);
      const drafts = await db.drafts.toArray();
      expect(drafts).toHaveLength(1); // Draft should still exist (not deleted by SyncManager)
    });

    it('should process completeDraft action', async () => {
      // Arrange
      const now = Date.now();
      const draftId = await db.drafts.add({
        sessionId: 's1',
        title: 'Draft 1',
        content: 'Content 1',
        tags: [],
        createdAt: now,
        status: 'draft',
      });
      await SyncManager.queueAction('completeDraft', { draftId });

      // Act
      await SyncManager.processQueue();

      // Assert
      // Assert
      const items = await db.syncQueue.toArray();
      expect(items).toHaveLength(0);
      const draft = await db.drafts.get(draftId);
      expect(draft?.status).toBe('draft'); // Status should default to draft (not updated by SyncManager)
    });

    it('should process pending items in order', async () => {
      const now = Date.now();
      // Queue 3 items
      await SyncManager.queueAction('deleteDraft', { draftId: 1 });
      await new Promise(resolve => setTimeout(resolve, 10));
      await SyncManager.queueAction('deleteDraft', { draftId: 2 });
      await new Promise(resolve => setTimeout(resolve, 10));
      await SyncManager.queueAction('deleteDraft', { draftId: 3 });

      // Create drafts for deletion
      await db.drafts.add({
        sessionId: 's1',
        title: 'Draft 1',
        content: 'Content 1',
        tags: [],
        createdAt: now,
        status: 'draft',
      });

      await db.drafts.add({
        sessionId: 's2',
        title: 'Draft 2',
        content: 'Content 2',
        tags: [],
        createdAt: now + 1,
        status: 'draft',
      });

      await db.drafts.add({
        sessionId: 's3',
        title: 'Draft 3',
        content: 'Content 3',
        tags: [],
        createdAt: now + 2,
        status: 'draft',
      });

      const draftsBefore = await db.drafts.count();
      expect(draftsBefore).toBe(3);

      // Process queue
      await SyncManager.processQueue();

      // All items should be processed (deleted from queue)
      const queueItems = await db.syncQueue.toArray();
      expect(queueItems).toHaveLength(0);

      // Drafts should be deleted
      const draftsAfter = await db.drafts.count();
      expect(draftsAfter).toBe(0);
    });

    it('should handle empty queue gracefully', async () => {
      await expect(SyncManager.processQueue()).resolves.not.toThrow();
    });

    it('should only process pending items', async () => {
      // Create a draft for the pending delete action to delete
      const draftId = await db.drafts.add({
        sessionId: 'test',
        title: 'Test',
        content: 'Test',
        tags: [],
        createdAt: Date.now(),
        status: 'draft',
      });

      // Add pending item
      await SyncManager.queueAction('deleteDraft', { draftId });

      // Add already processed item
      await db.syncQueue.add({
        action: 'saveDraft',
        payload: {
          draftData: {
            sessionId: 'test',
            title: 'Test',
            content: 'Test',
            tags: [],
            createdAt: Date.now(),
            status: 'draft',
          },
        },
        status: 'synced',
        createdAt: Date.now(),
        retries: 0,
      });

      // Add failed item
      await db.syncQueue.add({
        action: 'saveDraft',
        payload: {
          draftData: {
            sessionId: 'test2',
            title: 'Test2',
            content: 'Test2',
            tags: [],
            createdAt: Date.now(),
            status: 'draft',
          },
        },
        status: 'failed',
        createdAt: Date.now(),
        retries: 3,
      });

      await SyncManager.processQueue();

      // Only pending item should be processed and removed
      const remaining = await db.syncQueue.toArray();
      expect(remaining).toHaveLength(2); // synced and failed remain
      expect(remaining.every(item => item.status !== 'pending')).toBe(true);
    });
  });

  describe('retry logic', () => {
    it('should increment retry count on failure', async () => {
      // Mock executeAction to fail
      vi.spyOn(SyncManager as any, 'executeAction').mockRejectedValue(
        new Error('Simulated failure')
      );

      const id = await SyncManager.queueAction('saveDraft', {
        draftData: {
          sessionId: 'test',
          title: 'Test',
          content: 'Test',
          tags: [],
          createdAt: Date.now(),
          status: 'draft',
        },
      });

      // Process once - should increment retry count
      await SyncManager.processQueue();

      const item = await db.syncQueue.get(id);
      expect(item?.retries).toBe(1);
      expect(item?.status).toBe('pending'); // Ready for retry
      expect(item?.lastError).toBeUndefined(); // No error stored yet for retryable
    });

    it('should mark as failed and store error after max retries', async () => {
      // Mock executeAction to always fail
      vi.spyOn(SyncManager as any, 'executeAction').mockRejectedValue(
        new Error('Simulated failure')
      );

      const id = await SyncManager.queueAction('saveDraft', {
        draftData: {
          sessionId: 'test',
          title: 'Test',
          content: 'Test',
          tags: [],
          createdAt: Date.now(),
          status: 'draft',
        },
      });

      // Manually set retries to 2 and mark as processing to simulate a retry
      // The next failure will trigger max retries
      await db.syncQueue.update(id, { retries: 2, status: 'processing' });

      // Directly call executeItem (private method access via type assertion)
      // This simulates what happens when processing a retried item
      const item = await db.syncQueue.get(id);
      if (item) {
        await (SyncManager as any).executeItem(item);
      }

      const result = await db.syncQueue.get(id);
      expect(result?.retries).toBe(3);
      expect(result?.status).toBe('failed');
      expect(result?.lastError).toBe('Error: Simulated failure');
    });

    it('should remove item from queue on success', async () => {
      // No mock - let it succeed naturally
      const draftData = {
        sessionId: 'test',
        title: 'Test',
        content: 'Test',
        tags: [],
        createdAt: Date.now(),
        status: 'draft' as const,
      };

      await SyncManager.queueAction('saveDraft', { draftData });

      await SyncManager.processQueue();

      const items = await db.syncQueue.toArray();
      expect(items).toHaveLength(0); // Item removed on success

      const drafts = await db.drafts.toArray();
      expect(drafts).toHaveLength(0); // Draft NOT created by SyncManager
    });
  });

  describe('network status detection', () => {
    it('should detect online status correctly', () => {
      const isOnline = SyncManager.isOnline();
      expect(typeof isOnline).toBe('boolean');
    });

    it('should start network listeners', () => {
      expect(() => SyncManager.startNetworkListener()).not.toThrow();
    });
  });

  describe('executeAction', () => {
    it('should execute saveDraft action', async () => {
      const draftData = {
        sessionId: 'test-session',
        title: 'Test Draft',
        content: 'Test content',
        tags: ['test'],
        createdAt: Date.now(),
        status: 'draft' as const,
      };

      await SyncManager.queueAction('saveDraft', { draftData });

      const draftsBefore = await db.drafts.toArray();
      expect(draftsBefore).toHaveLength(0);

      await SyncManager.processQueue();

      // Item should be removed from queue after successful execution
      const queueItems = await db.syncQueue.toArray();
      expect(queueItems).toHaveLength(0);

      const draftsAfter = await db.drafts.toArray();
      expect(draftsAfter).toHaveLength(0); // No draft created
    });

    it('should execute deleteDraft action', async () => {
      // Create a draft first
      const draftId = await db.drafts.add({
        sessionId: 'test-session',
        title: 'Test Draft',
        content: 'Test content',
        tags: [],
        createdAt: Date.now(),
        status: 'draft',
      });

      await SyncManager.queueAction('deleteDraft', { draftId });

      expect(await db.drafts.get(draftId)).toBeDefined();

      await SyncManager.processQueue();

      // Item should be removed from queue after successful deletion
      const queueItems = await db.syncQueue.toArray();
      expect(queueItems).toHaveLength(0);

      expect(await db.drafts.get(draftId)).toBeDefined(); // Not deleted
    });

    it('should execute completeDraft action', async () => {
      const draftId = await db.drafts.add({
        sessionId: 'test-session',
        title: 'Test Draft',
        content: 'Test content',
        tags: [],
        createdAt: Date.now(),
        status: 'draft',
      });

      await SyncManager.queueAction('completeDraft', { draftId });

      const draftBefore = await db.drafts.get(draftId);
      expect(draftBefore?.status).toBe('draft');

      await SyncManager.processQueue();

      // Item should be removed from queue after successful completion
      const queueItems = await db.syncQueue.toArray();
      expect(queueItems).toHaveLength(0);

      const draftAfter = await db.drafts.get(draftId);
      expect(draftAfter?.status).toBe('draft'); // Not updated
    });

    it('should mark as failed for unknown action type', async () => {
      await db.syncQueue.add({
        action: 'unknown' as any,
        payload: {},
        status: 'pending',
        createdAt: Date.now(),
        retries: 0,
      });

      // Process should handle the error gracefully (increment retries, mark as pending)
      await SyncManager.processQueue();

      const items = await db.syncQueue.toArray();
      expect(items).toHaveLength(1);
      // After first failure, should be marked pending for retry
      expect(items[0].status).toBe('pending');
      expect(items[0].retries).toBe(1);
      expect(items[0].lastError).toBeUndefined(); // No error stored yet for retryable
    });
  });
});
