/**
 * ExtAppsAdapter Tests
 *
 * Tests for the ext-apps (SEP-1865) platform adapter, focusing on
 * correct platform detection and Claude exclusion.
 *
 * @jest-environment jsdom
 */

import { ExtAppsAdapter } from '../ext-apps.adapter';

describe('ExtAppsAdapter', () => {
  let adapter: ExtAppsAdapter;
  let originalWindow: typeof globalThis.window;

  beforeEach(() => {
    adapter = new ExtAppsAdapter();
    originalWindow = globalThis.window;
  });

  afterEach(() => {
    // Restore window properties
    if (typeof window !== 'undefined') {
      const win = window as any;
      delete win.__mcpPlatform;
      delete win.__extAppsInitialized;
      delete win.__mcpAppsEnabled;
      delete win.__claudeArtifact;
      delete win.claude;
      delete win.openai;
    }
  });

  describe('adapter properties', () => {
    it('should have correct id', () => {
      expect(adapter.id).toBe('ext-apps');
    });

    it('should have correct name', () => {
      expect(adapter.name).toBe('ext-apps (SEP-1865)');
    });

    it('should have priority 80', () => {
      expect(adapter.priority).toBe(80);
    });

    it('should have default capabilities', () => {
      expect(adapter.capabilities).toMatchObject({
        canPersistState: true,
        hasNetworkAccess: true,
        supportsTheme: true,
      });
    });
  });

  describe('canHandle', () => {
    describe('returns false when not in iframe', () => {
      it('should return false when window.parent equals window', () => {
        // By default in jsdom, window.parent === window (not in iframe)
        expect(adapter.canHandle()).toBe(false);
      });
    });

    describe('when in iframe context', () => {
      beforeEach(() => {
        // Simulate being in an iframe
        Object.defineProperty(window, 'parent', {
          value: { notSameAsWindow: true },
          configurable: true,
        });
      });

      afterEach(() => {
        // Restore parent to window (not in iframe)
        Object.defineProperty(window, 'parent', {
          value: window,
          configurable: true,
        });
      });

      it('should return false for generic iframe without ext-apps marker', () => {
        // Critical fix: should NOT return true for any iframe
        expect(adapter.canHandle()).toBe(false);
      });

      it('should return true when __mcpPlatform is ext-apps', () => {
        (window as any).__mcpPlatform = 'ext-apps';
        expect(adapter.canHandle()).toBe(true);
      });

      it('should return true when __extAppsInitialized is set', () => {
        (window as any).__extAppsInitialized = true;
        expect(adapter.canHandle()).toBe(true);
      });

      describe('Claude exclusion', () => {
        it('should return false when __mcpPlatform is claude', () => {
          (window as any).__mcpPlatform = 'claude';
          expect(adapter.canHandle()).toBe(false);
        });

        it('should return false when window.claude exists', () => {
          (window as any).claude = {};
          expect(adapter.canHandle()).toBe(false);
        });

        it('should return false when __claudeArtifact is set', () => {
          (window as any).__claudeArtifact = true;
          expect(adapter.canHandle()).toBe(false);
        });

        // Note: URL-based Claude detection (claude.ai/anthropic.com) cannot be
        // easily tested in jsdom as window.location is not redefinable.
        // The URL detection code is verified via the IIFE generator tests
        // which check that the generated code includes proper URL checks.
        // The critical behavior (Claude markers take precedence) is tested above.
      });

      describe('OpenAI exclusion', () => {
        it('should return false when openai.canvas exists', () => {
          (window as any).openai = { canvas: {} };
          expect(adapter.canHandle()).toBe(false);
        });

        it('should return false when openai.callTool is a function', () => {
          (window as any).openai = { callTool: () => {} };
          expect(adapter.canHandle()).toBe(false);
        });
      });

      describe('priority over Claude when explicit marker is set', () => {
        it('should prioritize ext-apps marker even if Claude markers exist', () => {
          const win = window as any;
          win.__mcpPlatform = 'ext-apps';
          // Even if Claude globals exist, the explicit ext-apps marker wins
          // (Claude check runs first, so this tests the order)
          expect(adapter.canHandle()).toBe(true);
        });
      });

      describe('Claude MCP Apps mode', () => {
        it('should return true when __mcpAppsEnabled is set (Claude MCP Apps)', () => {
          (window as any).__mcpAppsEnabled = true;
          expect(adapter.canHandle()).toBe(true);
        });

        it('should handle Claude MCP Apps even with Claude URL detected', () => {
          (window as any).__mcpAppsEnabled = true;
          // Note: URL detection happens after __mcpAppsEnabled check,
          // so MCP Apps mode takes precedence
          expect(adapter.canHandle()).toBe(true);
        });

        it('should handle Claude MCP Apps even with Claude globals present', () => {
          const win = window as any;
          win.__mcpAppsEnabled = true;
          win.claude = {};
          // MCP Apps flag takes precedence over legacy Claude detection
          expect(adapter.canHandle()).toBe(true);
        });
      });
    });
  });

  describe('canHandle in SSR context', () => {
    it('should return false when window is undefined', () => {
      // We can't easily mock window being undefined in jsdom,
      // but we can verify the first check in the method
      expect(typeof window).toBe('object');
      // The method should handle window being undefined gracefully
    });
  });

  describe('extended ext-apps methods', () => {
    let adapterWithConfig: ExtAppsAdapter;

    beforeEach(() => {
      adapterWithConfig = new ExtAppsAdapter();
    });

    describe('updateModelContext', () => {
      it('should throw ExtAppsNotSupportedError when modelContextUpdate capability is not present', async () => {
        // Host capabilities don't include modelContextUpdate
        // @ts-expect-error - accessing private property for testing
        adapterWithConfig._hostCapabilities = {};

        await expect(adapterWithConfig.updateModelContext({ key: 'value' })).rejects.toThrow(
          'Model context update not supported by host',
        );
      });

      it('should throw ExtAppsNotSupportedError when modelContextUpdate is false', async () => {
        // @ts-expect-error - accessing private property for testing
        adapterWithConfig._hostCapabilities = { modelContextUpdate: false };

        await expect(adapterWithConfig.updateModelContext({ key: 'value' })).rejects.toThrow(
          'Model context update not supported by host',
        );
      });
    });

    describe('log', () => {
      it('should fall back to console when host does not support logging', async () => {
        // @ts-expect-error - accessing private property for testing
        adapterWithConfig._hostCapabilities = { logging: false };

        const consoleSpy = jest.spyOn(console, 'info').mockImplementation();

        await adapterWithConfig.log('info', 'Test message', { extra: 'data' });

        expect(consoleSpy).toHaveBeenCalledWith('[Widget] Test message', { extra: 'data' });
        consoleSpy.mockRestore();
      });

      it('should use correct console method for each log level', async () => {
        // @ts-expect-error - accessing private property for testing
        adapterWithConfig._hostCapabilities = { logging: false };

        const debugSpy = jest.spyOn(console, 'debug').mockImplementation();
        const warnSpy = jest.spyOn(console, 'warn').mockImplementation();
        const errorSpy = jest.spyOn(console, 'error').mockImplementation();

        await adapterWithConfig.log('debug', 'Debug message');
        expect(debugSpy).toHaveBeenCalledWith('[Widget] Debug message', undefined);

        await adapterWithConfig.log('warn', 'Warn message');
        expect(warnSpy).toHaveBeenCalledWith('[Widget] Warn message', undefined);

        await adapterWithConfig.log('error', 'Error message');
        expect(errorSpy).toHaveBeenCalledWith('[Widget] Error message', undefined);

        debugSpy.mockRestore();
        warnSpy.mockRestore();
        errorSpy.mockRestore();
      });
    });

    describe('registerTool', () => {
      it('should throw ExtAppsNotSupportedError when widgetTools capability is not present', async () => {
        // @ts-expect-error - accessing private property for testing
        adapterWithConfig._hostCapabilities = {};

        await expect(adapterWithConfig.registerTool('my_tool', 'A tool', { type: 'object' })).rejects.toThrow(
          'Widget tool registration not supported by host',
        );
      });

      it('should throw ExtAppsNotSupportedError when widgetTools is false', async () => {
        // @ts-expect-error - accessing private property for testing
        adapterWithConfig._hostCapabilities = { widgetTools: false };

        await expect(adapterWithConfig.registerTool('my_tool', 'A tool', { type: 'object' })).rejects.toThrow(
          'Widget tool registration not supported by host',
        );
      });
    });

    describe('unregisterTool', () => {
      it('should throw ExtAppsNotSupportedError when widgetTools capability is not present', async () => {
        // @ts-expect-error - accessing private property for testing
        adapterWithConfig._hostCapabilities = {};

        await expect(adapterWithConfig.unregisterTool('my_tool')).rejects.toThrow(
          'Widget tool unregistration not supported by host',
        );
      });

      it('should throw ExtAppsNotSupportedError when widgetTools is false', async () => {
        // @ts-expect-error - accessing private property for testing
        adapterWithConfig._hostCapabilities = { widgetTools: false };

        await expect(adapterWithConfig.unregisterTool('my_tool')).rejects.toThrow(
          'Widget tool unregistration not supported by host',
        );
      });
    });
  });
});
