/**
 * @fileoverview Integration tests for SSR and Hydration
 */

import { beforeEach, describe, expect, it, vi } from 'vitest';
import { OrdoJSRuntime } from '../runtime/index.js';
import { OrdoJSSSR } from './ssr-engine.js';

// Mock DOM environment
const mockDocument = {
  readyState: 'complete',
  addEventListener: vi.fn(),
  getElementById: vi.fn(),
  querySelector: vi.fn(),
  querySelectorAll: vi.fn()
};

Object.defineProperty(global, 'document', {
  value: mockDocument,
  writable: true
});

describe('SSR and Hydration Integration', () => {
  let ssr: OrdoJSSSR;
  let runtime: OrdoJSRuntime;

  // Mock component AST
  const mockAST: any = {
    component: {
      name: 'CounterComponent',
      props: [
        { name: 'initialCount', isRequired: false, defaultValue: { expressionType: 'LITERAL', value: 0 } }
      ],
      clientBlock: {
        reactiveVariables: [
          {
            name: 'count',
            initialValue: { expressionType: 'LITERAL', value: 0 },
            dataType: { name: 'number', isArray: false, isOptional: false, genericTypes: [] }
          }
        ]
      },
      serverBlock: {
        functions: [
          {
            name: 'getServerSideProps',
            parameters: [],
            body: [],
            returnType: { name: 'object', isArray: false, isOptional: false, genericTypes: [] },
            isAsync: true,
            isPublic: true,
            middleware: [],
            permissions: []
          }
        ]
      },
      markupBlock: {
        elements: [],
        textNodes: [],
        interpolations: []
      }
    },
    dependencies: [],
    exports: [],
    sourceMap: {
      version: 3,
      sources: [],
      names: [],
      mappings: '',
      sourcesContent: []
    }
  };

  beforeEach(() => {
    // Reset singleton instance
    (OrdoJSRuntime as any).instance = undefined;

    ssr = new OrdoJSSSR({
      includeHydrationMarkers: true,
      includeHydrationData: true
    });

    runtime = OrdoJSRuntime.getInstance();

    // Reset mocks
    vi.clearAllMocks();
  });

  it('should render component on server and prepare for hydration', async () => {
    // Register component for SSR
    ssr.registerComponent(mockAST);

    // Render component with initial props
    const serverHtml = await ssr.renderComponent('CounterComponent', { initialCount: 5 });

    // Verify server-rendered HTML contains hydration markers
    expect(serverHtml).toContain('data-ordojs-component="CounterComponent"');
    expect(serverHtml).toContain('data-component-id');
    expect(serverHtml).toContain('<script type="application/json" id="ordojs-hydration-data">');

    // Parse hydration data from HTML
    const hydrationDataMatch = serverHtml.match(/<script type="application\/json" id="ordojs-hydration-data">\s*([\s\S]*?)\s*<\/script>/);
    expect(hydrationDataMatch).toBeTruthy();

    if (hydrationDataMatch) {
      const hydrationData = JSON.parse(hydrationDataMatch[1]);
      expect(hydrationData.componentName).toBe('CounterComponent');
      expect(hydrationData.props.initialCount).toBe(5);
      expect(hydrationData.initialState.count).toBe(0);
    }
  });

  it('should hydrate server-rendered component on client', async () => {
    // Register component for SSR
    ssr.registerComponent(mockAST);

    // Render component on server
    const serverHtml = await ssr.renderComponent('CounterComponent', { initialCount: 10 });

    // Extract hydration data
    const hydrationDataMatch = serverHtml.match(/<script type="application\/json" id="ordojs-hydration-data">\s*([\s\S]*?)\s*<\/script>/);
    const hydrationData = JSON.parse(hydrationDataMatch![1]);

    // Mock DOM elements for client-side hydration
    const mockHydrationScript = {
      textContent: JSON.stringify(hydrationData)
    };

    const mockComponentElement = {
      getAttribute: vi.fn((attr) => {
        if (attr === 'data-ordojs-component') return 'CounterComponent';
        if (attr === 'data-component-id') return hydrationData.componentId;
        return null;
      }),
      setAttribute: vi.fn(),
      querySelectorAll: vi.fn().mockReturnValue([])
    };

    mockDocument.getElementById.mockReturnValue(mockHydrationScript);
    mockDocument.querySelector.mockReturnValue(mockComponentElement);
    mockDocument.querySelectorAll.mockReturnValue([]);

    // Mock component constructor for client-side
    const mockConstructor = vi.fn().mockReturnValue({
      id: hydrationData.componentId,
      name: 'CounterComponent',
      element: mockComponentElement,
      props: hydrationData.props,
      state: {},
      eventListeners: new Map(),
      update: vi.fn(),
      unmount: vi.fn()
    });

    // Register component constructor for hydration
    runtime.registerComponent('CounterComponent', mockConstructor);

    // Perform client-side hydration
    runtime.autoHydrate();

    // Verify component was hydrated with correct props
    expect(mockConstructor).toHaveBeenCalledWith({ initialCount: 10 });
    expect(mockComponentElement.setAttribute).toHaveBeenCalledWith('data-ordojs-hydrated', 'true');

    // Verify component is registered in runtime
    const hydratedComponent = runtime.getComponent(hydrationData.componentId);
    expect(hydratedComponent).toBeTruthy();
    expect(hydratedComponent?.name).toBe('CounterComponent');
  });

  it('should handle route-based SSR with hydration', async () => {
    // Configure SSR with routes
    const ssrWithRoutes = new OrdoJSSSR({
      includeHydrationMarkers: true,
      includeHydrationData: true,
      routes: [
        {
          path: '/counter/:initialValue',
          component: 'CounterComponent',
          dataFetcher: async (params) => {
            return {
              initialCount: parseInt(params.initialValue, 10),
              serverTime: new Date().toISOString()
            };
          }
        }
      ]
    });

    // Register component
    ssrWithRoutes.registerComponent(mockAST);

    // Render route
    const routeHtml = await ssrWithRoutes.renderRoute('/counter/42');

    // Verify route data was included
    expect(routeHtml).toContain('data-ordojs-component="CounterComponent"');

    // Parse hydration data
    const hydrationDataMatch = routeHtml.match(/<script type="application\/json" id="ordojs-hydration-data">\s*([\s\S]*?)\s*<\/script>/);
    const hydrationData = JSON.parse(hydrationDataMatch![1]);

    expect(hydrationData.props.initialCount).toBe(42);
    expect(hydrationData.props.routeParams.initialValue).toBe('42');
    expect(hydrationData.props.serverTime).toBeTruthy();
  });

  it('should generate complete HTML document with hydration', () => {
    const componentHtml = '<div data-ordojs-component="CounterComponent">Counter: 0</div>';
    const document = ssr.generateDocument(
      componentHtml,
      'Counter App',
      ['/js/counter.js'],
      ['/css/counter.css']
    );

    // Verify complete HTML structure
    expect(document).toContain('<!DOCTYPE html>');
    expect(document).toContain('<title>Counter App</title>');
    expect(document).toContain('<div id="app">');
    expect(document).toContain('data-ordojs-component="CounterComponent"');
    expect(document).toContain('<script src="/js/counter.js" defer></script>');
    expect(document).toContain('<link rel="stylesheet" href="/css/counter.css">');
  });

  it('should handle component unmounting after hydration', async () => {
    // Register and render component
    ssr.registerComponent(mockAST);
    const serverHtml = await ssr.renderComponent('CounterComponent');

    // Extract hydration data and set up client-side mocks
    const hydrationDataMatch = serverHtml.match(/<script type="application\/json" id="ordojs-hydration-data">\s*([\s\S]*?)\s*<\/script>/);
    const hydrationData = JSON.parse(hydrationDataMatch![1]);

    const mockComponentElement = {
      getAttribute: vi.fn((attr) => {
        if (attr === 'data-ordojs-component') return 'CounterComponent';
        if (attr === 'data-component-id') return hydrationData.componentId;
        return null;
      }),
      setAttribute: vi.fn(),
      removeAttribute: vi.fn(),
      querySelectorAll: vi.fn().mockReturnValue([])
    };

    mockDocument.getElementById.mockReturnValue({ textContent: JSON.stringify(hydrationData) });
    mockDocument.querySelector.mockReturnValue(mockComponentElement);
    mockDocument.querySelectorAll.mockReturnValue([]);

    const mockUnmount = vi.fn();
    const mockConstructor = vi.fn().mockReturnValue({
      id: hydrationData.componentId,
      name: 'CounterComponent',
      element: mockComponentElement,
      props: {},
      state: {},
      eventListeners: new Map(),
      update: vi.fn(),
      unmount: mockUnmount
    });

    runtime.registerComponent('CounterComponent', mockConstructor);
    runtime.autoHydrate();

    // Verify component was hydrated
    expect(runtime.getComponent(hydrationData.componentId)).toBeTruthy();

    // Unmount component
    runtime.unmountComponent(hydrationData.componentId);

    // Verify component was properly unmounted
    expect(mockUnmount).toHaveBeenCalled();
    expect(mockComponentElement.removeAttribute).toHaveBeenCalledWith('data-ordojs-hydrated');
    expect(runtime.getComponent(hydrationData.componentId)).toBeUndefined();
  });
});
