import { test, expect } from '@playwright/test';
import { ModelerPage } from './pages/ModelerPage';
import { setupMocks, mockModel } from './fixtures/mocks';

test.describe('Workflow Modeler - Core Functionality', () => {
  let modeler: ModelerPage;

  test.beforeEach(async ({ page }) => {
    await setupMocks(page);
    modeler = new ModelerPage(page);
  });

  test.describe('Initial Load', () => {
    test('should load the modeler with canvas visible', async ({ page }) => {
      await modeler.goto();

      // Canvas should be visible
      await expect(modeler.canvas).toBeVisible();

      // Save button should be visible but disabled (no unsaved changes)
      await expect(modeler.saveButton).toBeVisible();
      await expect(modeler.saveButton).toBeDisabled();
    });

    test('should display existing nodes from model', async ({ page }) => {
      await modeler.goto();

      // Model has 2 nodes
      const nodeCount = await modeler.getNodeCount();
      expect(nodeCount).toBe(2);
    });

    test('should display existing edges from model', async ({ page }) => {
      await modeler.goto();

      // Model has 1 edge
      const edgeCount = await modeler.getEdgeCount();
      expect(edgeCount).toBe(1);
    });
  });

  test.describe('Node Selection', () => {
    test('should select a node when clicked', async ({ page }) => {
      await modeler.goto();
      await modeler.selectNode('event_1');

      const node = modeler.getNode('event_1');
      await expect(node).toHaveClass(/selected/);
    });

    test('should show property panel when node is selected', async ({ page }) => {
      await modeler.goto();
      await modeler.selectNode('event_1');

      await expect(modeler.propertyPanel).toBeVisible();
      const title = await modeler.getPropertyPanelTitle();
      expect(title).toContain('Event');
    });

    test('should allow multi-select with Shift+Click', async ({ page }) => {
      await modeler.goto();
      await modeler.selectMultipleNodes(['event_1', 'action_1']);

      const event = modeler.getNode('event_1');
      const action = modeler.getNode('action_1');

      await expect(event).toHaveClass(/selected/);
      await expect(action).toHaveClass(/selected/);
    });

    test('should deselect all when clicking canvas background', async ({ page }) => {
      await modeler.goto();
      await modeler.selectNode('event_1');
      await modeler.canvas.click({ position: { x: 50, y: 50 } });

      const node = modeler.getNode('event_1');
      await expect(node).not.toHaveClass(/selected/);
    });

    test('should not mark model as changed when selecting a node', async ({ page }) => {
      await modeler.goto();

      // Save button starts disabled (no unsaved changes)
      await expect(modeler.saveButton).toBeDisabled();

      // Select a node
      await modeler.selectNode('event_1');
      await expect(modeler.getNode('event_1')).toHaveClass(/selected/);

      // Save button should still be disabled — selection alone is not a change
      await expect(modeler.saveButton).toBeDisabled();
    });

    test('should not mark model as changed when selecting multiple nodes', async ({ page }) => {
      await modeler.goto();
      await expect(modeler.saveButton).toBeDisabled();

      await modeler.selectMultipleNodes(['event_1', 'action_1']);
      await expect(modeler.getNode('event_1')).toHaveClass(/selected/);
      await expect(modeler.getNode('action_1')).toHaveClass(/selected/);

      // Multi-selection should not mark the model as changed
      await expect(modeler.saveButton).toBeDisabled();
    });

    test('should not mark model as changed when deselecting nodes', async ({ page }) => {
      await modeler.goto();
      await expect(modeler.saveButton).toBeDisabled();

      // Select then deselect by clicking canvas background
      await modeler.selectNode('event_1');
      await modeler.canvas.click({ position: { x: 50, y: 50 } });

      await expect(modeler.saveButton).toBeDisabled();
    });
  });

  test.describe('Node Manipulation', () => {
    test('should delete selected node with Delete key', async ({ page }) => {
      await modeler.goto();
      const initialCount = await modeler.getNodeCount();

      await modeler.selectNode('action_1');
      await modeler.deleteSelected();

      const finalCount = await modeler.getNodeCount();
      expect(finalCount).toBe(initialCount - 1);
    });

    test('should mark model as changed when dragging a node to a new position', async ({ page }) => {
      await modeler.goto();
      await expect(modeler.saveButton).toBeDisabled();

      await modeler.moveNode('event_1', 100, 50);
      await page.waitForTimeout(300);

      // Dragging a node to a new position is a real change
      await expect(modeler.saveButton).toBeEnabled();
    });

    test.skip('should move node by dragging', async ({ page }) => {
      // Drag behavior can be flaky in headless mode
      await modeler.goto();

      const nodeBefore = modeler.getNode('event_1');
      const boxBefore = await nodeBefore.boundingBox();

      await modeler.moveNode('event_1', 100, 50);

      const boxAfter = await nodeBefore.boundingBox();

      expect(boxAfter!.x).toBeCloseTo(boxBefore!.x + 100, 0);
      expect(boxAfter!.y).toBeCloseTo(boxBefore!.y + 50, 0);
    });
  });
});

// Component Panel tests are skipped because the panel is hidden behind a feature flag
// See: constants/featureFlags.ts - SHOW_COMPONENT_PANEL is set to false
test.describe.skip('Workflow Modeler - Component Panel', () => {
  let modeler: ModelerPage;

  test.beforeEach(async ({ page }) => {
    await setupMocks(page);
    modeler = new ModelerPage(page);
    await modeler.goto();
    // Expand component panel for tests (may be collapsed for existing models)
    await modeler.expandComponentPanel();
  });

  test('should display component categories', async ({ page }) => {
    // Component type selector should show category tabs
    const typeSelector = page.locator('.component-type-selector');
    await expect(typeSelector).toBeVisible();

    // Should have at least one category tab
    const categoryTabs = typeSelector.locator('button');
    expect(await categoryTabs.count()).toBeGreaterThan(0);
  });

  test('should switch between component categories', async ({ page }) => {
    // Click on Actions category
    const actionsTab = page.locator('.component-type-selector button:has-text("Actions")');
    await actionsTab.click();

    // Should show action components
    const saveComponent = modeler.getComponent('Save Entity');
    await expect(saveComponent).toBeVisible();
  });

  test('should filter components by search', async ({ page }) => {
    // First switch to Actions to see components
    const actionsTab = page.locator('.component-type-selector button:has-text("Actions")');
    await actionsTab.click();

    await modeler.searchComponents('message');

    // Should show Set Message action (matches search)
    const messageComponent = modeler.getComponent('Set Message');
    await expect(messageComponent).toBeVisible();

    // Should not show Save Entity (doesn't match search)
    const saveComponent = modeler.getComponent('Save Entity');
    await expect(saveComponent).not.toBeVisible();
  });

  test('should clear search and show all components', async ({ page }) => {
    // Switch to Events category
    const eventsTab = page.locator('.component-type-selector button:has-text("Events")');
    await eventsTab.click();

    await modeler.searchComponents('cron');
    await modeler.clearComponentSearch();

    // All events should be visible again
    const cronComponent = modeler.getComponent('Cron Run');
    await expect(cronComponent).toBeVisible();

    const loginComponent = modeler.getComponent('User Login');
    await expect(loginComponent).toBeVisible();
  });
});

// Drag and Drop tests from Component Panel are skipped because the panel is hidden
// See: constants/featureFlags.ts - SHOW_COMPONENT_PANEL is set to false
test.describe.skip('Workflow Modeler - Drag and Drop', () => {
  let modeler: ModelerPage;

  test.beforeEach(async ({ page }) => {
    await setupMocks(page);
    modeler = new ModelerPage(page);
    await modeler.goto();
    // Expand component panel for drag and drop tests
    await modeler.expandComponentPanel();
  });

  test('should add new node by dragging component to canvas', async ({ page }) => {
    const initialCount = await modeler.getNodeCount();

    // Switch to Actions category first
    const actionsTab = page.locator('.component-type-selector button:has-text("Actions")');
    await actionsTab.click();

    await modeler.dragComponentToCanvas('Set Message', 300, 300);

    const finalCount = await modeler.getNodeCount();
    expect(finalCount).toBe(initialCount + 1);
  });

  test.skip('should create edge between nodes', async ({ page }) => {
    // This test is complex and requires specific node handle positions
    // Skipping for now - drag-to-connect behavior is tested manually

    // Switch to Actions category first
    const actionsTab = page.locator('.component-type-selector button:has-text("Actions")');
    await actionsTab.click();

    // Add a new node first
    await modeler.dragComponentToCanvas('Set Message', 600, 200);

    // Wait for the node to be added
    await page.waitForTimeout(500);

    const initialEdgeCount = await modeler.getEdgeCount();

    // Connect action_1 to the new node
    const nodes = await modeler.nodes.all();
    const lastNode = nodes[nodes.length - 1];
    const lastNodeId = await lastNode.getAttribute('data-id');

    if (lastNodeId) {
      await modeler.connectNodes('action_1', lastNodeId);

      const finalEdgeCount = await modeler.getEdgeCount();
      expect(finalEdgeCount).toBeGreaterThanOrEqual(initialEdgeCount);
    }
  });
});

// Some keyboard shortcuts need selector adjustments
// Copy/paste works, but undo/search/lock tests need updated selectors
test.describe('Workflow Modeler - Keyboard Shortcuts', () => {
  let modeler: ModelerPage;

  test.beforeEach(async ({ page }) => {
    await setupMocks(page);
    modeler = new ModelerPage(page);
    await modeler.goto();
  });

  test('should copy and paste selected node', async ({ page }) => {
    const initialCount = await modeler.getNodeCount();

    await modeler.selectNode('action_1');
    await modeler.copySelected();
    await modeler.paste();

    const finalCount = await modeler.getNodeCount();
    expect(finalCount).toBe(initialCount + 1);
  });

  test.skip('should undo last action', async ({ page }) => {
    // Undo requires undo stack which may not be available in isolated test
    await modeler.selectNode('action_1');
    await modeler.deleteSelected();

    const countAfterDelete = await modeler.getNodeCount();
    expect(countAfterDelete).toBe(1);

    await modeler.undo();

    const countAfterUndo = await modeler.getNodeCount();
    expect(countAfterUndo).toBe(2);
  });

  test('should redo undone action', async ({ page }) => {
    await modeler.selectNode('action_1');
    await modeler.deleteSelected();
    await modeler.undo();
    await modeler.redo();

    const finalCount = await modeler.getNodeCount();
    expect(finalCount).toBe(1);
  });

  test('should open search with Ctrl+F', async ({ page }) => {
    await modeler.openSearch();

    // Search is an inline input in the toolbar
    const searchInput = page.locator('input[placeholder*="Search components"]');
    await expect(searchInput).toBeVisible();
  });

  test.skip('should toggle node lock mode with Ctrl+L', async ({ page }) => {
    // Keyboard shortcuts may not work consistently in all test environments
    await modeler.canvas.click();
    await modeler.toggleNodeLock();

    const lockButton = page.locator('button[title="Unlock Canvas"]');
    await expect(lockButton).toBeVisible();

    await modeler.toggleNodeLock();
    const unlockButton = page.locator('button[title="Lock Canvas"]');
    await expect(unlockButton).toBeVisible();
  });
});

// Toolbar tests - some need API integration for full testing
test.describe('Workflow Modeler - Toolbar Actions', () => {
  let modeler: ModelerPage;

  test.beforeEach(async ({ page }) => {
    await setupMocks(page);
    modeler = new ModelerPage(page);
    await modeler.goto();
  });

  test('should save model when save button clicked', async ({ page }) => {
    // Save button starts disabled (no unsaved changes)
    await expect(modeler.saveButton).toBeDisabled();

    // Make a change to enable the save button (auto layout marks model as changed)
    await modeler.autoLayout();
    await page.waitForTimeout(300);
    await expect(modeler.saveButton).toBeEnabled();

    // Set up request interception before clicking
    const savePromise = page.waitForRequest(request =>
      request.url().includes('modeler-api/model') &&
      (request.method() === 'POST' || request.method() === 'PUT')
    );

    await modeler.saveButton.click();

    // Wait for the save request (with timeout)
    const request = await Promise.race([
      savePromise,
      page.waitForTimeout(2000).then(() => null)
    ]);

    // Either request was made or timeout - both acceptable for basic test
    expect(modeler.saveButton).toBeVisible();
  });

  test('should open settings modal', async () => {
    await modeler.openSettings();

    const modal = modeler.page.locator('.metadata-modal');
    await expect(modal).toBeVisible();
  });

  test('should close modal with Escape key', async ({ page }) => {
    await modeler.openSettings();

    // Find and click the close button in the modal
    const closeButton = page.locator('.metadata-modal button.close-btn, .metadata-modal-header button');
    if (await closeButton.count() > 0) {
      await closeButton.first().click();
    } else {
      await modeler.closeModal(); // Fall back to Escape key
    }

    // Wait for modal to close
    await page.waitForTimeout(300);
    const modal = page.locator('.metadata-modal');
    await expect(modal).not.toBeVisible();
  });

  test('should trigger auto-layout', async ({ page }) => {
    const nodeBefore = modeler.getNode('event_1');
    const boxBefore = await nodeBefore.boundingBox();

    await modeler.autoLayout();

    // Wait for layout animation
    await page.waitForTimeout(300);

    const boxAfter = await nodeBefore.boundingBox();

    // Position should have changed (unless already optimal)
    // This is a basic check - actual validation depends on layout algorithm
    expect(boxAfter).toBeDefined();
  });
});

// Canvas controls - minimap may be hidden by default
test.describe.skip('Workflow Modeler - Canvas Controls', () => {
  let modeler: ModelerPage;

  test.beforeEach(async ({ page }) => {
    await setupMocks(page);
    modeler = new ModelerPage(page);
    await modeler.goto();
  });

  test('should display minimap', async () => {
    await expect(modeler.minimap).toBeVisible();
  });

  test('should display controls (zoom buttons)', async () => {
    await expect(modeler.controls).toBeVisible();
  });

  test('should zoom canvas with mouse wheel', async ({ page }) => {
    // This test verifies zoom functionality is available
    // Actual zoom verification would require viewport transform checks
    await modeler.canvas.click();
    await modeler.zoom(-100); // Zoom in

    // Canvas should still be visible after zoom
    await expect(modeler.canvas).toBeVisible();
  });

  test('should fit view to show all nodes', async () => {
    await modeler.fitView();

    // All nodes should be visible after fit view
    const event = modeler.getNode('event_1');
    const action = modeler.getNode('action_1');

    await expect(event).toBeVisible();
    await expect(action).toBeVisible();
  });
});
