import { describe, expect, it } from 'bun:test';
import { runMcpRequests, getResponseText, mkdtempSync, writeFileSync, tmpdir, join } from './browser-mcp-test-harness';
import type { MockPageState } from './browser-mcp-test-harness';

describe('ccs-browser MCP server - downloads and file inputs', () => {
  it('applies browser-scoped download behavior, records download summaries, and cancels an in-progress download', async () => {
    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Reports',
        currentUrl: 'https://example.com/reports',
        browser: {},
        events: {
          downloads: [
            {
              guid: 'download-guid-1',
              url: 'https://example.com/files/report.csv',
              suggestedFilename: 'report.csv',
              progress: [{ receivedBytes: 5, totalBytes: 10, state: 'inProgress' }],
            },
          ],
        },
      },
    ];

    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 57,
          method: 'tools/call',
          params: {
            name: 'browser_set_download_behavior',
            arguments: { behavior: 'accept', eventsEnabled: true },
          },
        },
        {
          jsonrpc: '2.0',
          id: 58,
          method: 'tools/call',
          params: { name: 'browser_list_downloads', arguments: {} },
        },
        {
          jsonrpc: '2.0',
          id: 59,
          method: 'tools/call',
          params: {
            name: 'browser_cancel_download',
            arguments: { guid: 'download-guid-1' },
          },
        },
        {
          jsonrpc: '2.0',
          id: 60,
          method: 'tools/call',
          params: { name: 'browser_list_downloads', arguments: {} },
        },
      ],
      { responseTimeoutMs: 12000 }
    );

    expect(getResponseText(responses.find((message) => message.id === 57))).toContain('scope: browser');
    expect(getResponseText(responses.find((message) => message.id === 58))).toContain('suggestedFilename: report.csv');
    expect(getResponseText(responses.find((message) => message.id === 60))).toContain('status: canceled');
    expect(pages[0]?.browser?.setDownloadBehaviorCalls?.[0]?.behavior).toBe('allow');
    expect(pages[0]?.browser?.canceledDownloadGuids).toContain('download-guid-1');
  });

  it('rejects browser_set_download_behavior when behavior is deny and downloadPath is provided', async () => {
    const responses = await runMcpRequests(
      [{ id: 'page-1', title: 'Reports', currentUrl: 'https://example.com/reports' }],
      [
        {
          jsonrpc: '2.0',
          id: 61,
          method: 'tools/call',
          params: {
            name: 'browser_set_download_behavior',
            arguments: {
              behavior: 'deny',
              downloadPath: '/tmp/blocked-downloads',
            },
          },
        },
      ]
    );

    const response = responses.find((message) => message.id === 61);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain(
      'Browser MCP failed: downloadPath is only allowed when behavior=accept'
    );
  });

  it('rejects browser_cancel_download for completed downloads', async () => {
    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Reports',
        currentUrl: 'https://example.com/reports',
        browser: {},
        events: {
          downloads: [
            {
              guid: 'download-guid-complete',
              url: 'https://example.com/files/report.csv',
              suggestedFilename: 'report.csv',
              progress: [{ receivedBytes: 10, totalBytes: 10, state: 'completed' }],
            },
          ],
        },
      },
    ];

    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 610,
          method: 'tools/call',
          params: {
            name: 'browser_set_download_behavior',
            arguments: { behavior: 'accept', eventsEnabled: true },
          },
        },
        {
          jsonrpc: '2.0',
          id: 611,
          method: 'tools/call',
          params: { name: 'browser_list_downloads', arguments: {} },
        },
        {
          jsonrpc: '2.0',
          id: 612,
          method: 'tools/call',
          params: {
            name: 'browser_cancel_download',
            arguments: { guid: 'download-guid-complete' },
          },
        },
      ],
      { responseTimeoutMs: 12000 }
    );

    expect(getResponseText(responses.find((message) => message.id === 611))).toContain('status: completed');
    const response = responses.find((message) => message.id === 612);
    expect((response?.result as { isError?: boolean }).isError).toBe(true);
    expect(getResponseText(response)).toContain(
      'Browser MCP failed: download is not cancelable in status: completed'
    );
    expect(pages[0]?.browser?.canceledDownloadGuids).toBeUndefined();
  });

  it('waits for a matching download event with browser_wait_for_event after Phase 8 changes', async () => {
    const responses = await runMcpRequests(
      [
        {
          id: 'page-1',
          title: 'Event Page',
          currentUrl: 'https://example.com/',
          events: {
            downloads: [
              {
                guid: 'download-guid-2',
                url: 'https://example.com/files/export.zip',
                suggestedFilename: 'export.zip',
              },
            ],
          },
        },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 62,
          method: 'tools/call',
          params: {
            name: 'browser_wait_for_event',
            arguments: {
              timeoutMs: 1000,
              event: { kind: 'download', suggestedFilenameIncludes: 'export.zip' },
            },
          },
        },
      ],
      { responseTimeoutMs: 12000 }
    );

    expect(getResponseText(responses.find((message) => message.id === 62))).toContain('status: observed');
  });

  it('sets files on selected-page, frameSelector, and pierceShadow file inputs', async () => {
    const tempDir = mkdtempSync(join(tmpdir(), 'ccs-browser-upload-'));
    const invoicePath = join(tempDir, 'invoice.pdf');
    const receiptPath = join(tempDir, 'receipt.png');
    writeFileSync(invoicePath, 'invoice');
    writeFileSync(receiptPath, 'receipt');

    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Root Uploads',
        currentUrl: 'https://example.com/root',
        fileInputs: {
          '#root-upload': { kind: 'file', multiple: true },
        },
        frames: [
          {
            selector: '#upload-frame',
            fileInputs: {
              '#frame-upload': { kind: 'file', multiple: true },
            },
          },
        ],
        shadowRoots: [
          {
            hostSelector: 'upload-panel',
            fileInputs: {
              '#shadow-upload': { kind: 'file' },
            },
          },
        ],
      },
      {
        id: 'page-2',
        title: 'Selected Uploads',
        currentUrl: 'https://example.com/selected',
        fileInputs: {
          '#selected-upload': { kind: 'file', multiple: true },
        },
      },
    ];

    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 63,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageIndex: 1 } },
        },
        {
          jsonrpc: '2.0',
          id: 64,
          method: 'tools/call',
          params: {
            name: 'browser_set_file_input',
            arguments: { selector: '#selected-upload', files: [invoicePath, receiptPath] },
          },
        },
        {
          jsonrpc: '2.0',
          id: 65,
          method: 'tools/call',
          params: {
            name: 'browser_set_file_input',
            arguments: {
              pageIndex: 0,
              selector: '#frame-upload',
              files: [invoicePath],
              frameSelector: '#upload-frame',
            },
          },
        },
        {
          jsonrpc: '2.0',
          id: 66,
          method: 'tools/call',
          params: {
            name: 'browser_set_file_input',
            arguments: {
              pageIndex: 0,
              selector: '#shadow-upload',
              files: [receiptPath],
              pierceShadow: true,
            },
          },
        },
      ]
    );

    expect(getResponseText(responses.find((message) => message.id === 64))).toContain('pageIndex: 1');
    expect(getResponseText(responses.find((message) => message.id === 65))).toContain('frameSelector: #upload-frame');
    expect(getResponseText(responses.find((message) => message.id === 66))).toContain('pierceShadow: true');

    expect((pages[1]?.fileInputs?.['#selected-upload'] as MockFileInputState).assignedFiles).toEqual([
      invoicePath,
      receiptPath,
    ]);
    expect(
      (pages[0]?.frames?.[0]?.fileInputs?.['#frame-upload'] as MockFileInputState).assignedFiles
    ).toEqual([invoicePath]);
    expect(
      (pages[0]?.shadowRoots?.[0]?.fileInputs?.['#shadow-upload'] as MockFileInputState).assignedFiles
    ).toEqual([receiptPath]);
  });

  it('uses pageId for browser_set_file_input when provided', async () => {
    const tempDir = mkdtempSync(join(tmpdir(), 'ccs-browser-upload-'));
    const assetPath = join(tempDir, 'asset.txt');
    writeFileSync(assetPath, 'asset');

    const pages: MockPageState[] = [
      {
        id: 'page-1',
        title: 'Selected Uploads',
        currentUrl: 'https://example.com/selected',
        fileInputs: {
          '#selected-upload': { kind: 'file' },
        },
      },
      {
        id: 'page-2',
        title: 'Explicit Uploads',
        currentUrl: 'https://example.com/explicit',
        fileInputs: {
          '#pageid-upload': { kind: 'file' },
        },
      },
    ];

    const responses = await runMcpRequests(
      pages,
      [
        {
          jsonrpc: '2.0',
          id: 67,
          method: 'tools/call',
          params: { name: 'browser_select_page', arguments: { pageId: 'page-1' } },
        },
        {
          jsonrpc: '2.0',
          id: 68,
          method: 'tools/call',
          params: {
            name: 'browser_set_file_input',
            arguments: { pageId: 'page-2', selector: '#pageid-upload', files: [assetPath] },
          },
        },
      ]
    );

    expect(getResponseText(responses.find((message) => message.id === 68))).toContain('pageIndex: 1');
    expect((pages[1]?.fileInputs?.['#pageid-upload'] as MockFileInputState).assignedFiles).toEqual([
      assetPath,
    ]);
    expect((pages[0]?.fileInputs?.['#selected-upload'] as MockFileInputState).assignedFiles).toBeUndefined();
  });

  it('rejects browser_set_file_input when target is not a file input, local file is missing, or page selectors conflict', async () => {
    const tempDir = mkdtempSync(join(tmpdir(), 'ccs-browser-upload-'));
    const okPath = join(tempDir, 'ok.txt');
    const missingPath = join(tempDir, 'missing.txt');
    writeFileSync(okPath, 'ok');

    const responses = await runMcpRequests(
      [
        {
          id: 'page-1',
          title: 'Upload Errors',
          currentUrl: 'https://example.com/',
          fileInputs: {
            '#real-file-input': { kind: 'file' },
            '#not-file-input': { kind: 'nonfile' },
          },
        },
      ],
      [
        {
          jsonrpc: '2.0',
          id: 69,
          method: 'tools/call',
          params: {
            name: 'browser_set_file_input',
            arguments: { selector: '#not-file-input', files: [okPath] },
          },
        },
        {
          jsonrpc: '2.0',
          id: 70,
          method: 'tools/call',
          params: {
            name: 'browser_set_file_input',
            arguments: { selector: '#real-file-input', files: [missingPath] },
          },
        },
        {
          jsonrpc: '2.0',
          id: 71,
          method: 'tools/call',
          params: {
            name: 'browser_set_file_input',
            arguments: { pageIndex: 0, pageId: 'page-1', selector: '#real-file-input', files: [okPath] },
          },
        },
      ]
    );

    expect(getResponseText(responses.find((message) => message.id === 69))).toContain(
      'element is not a file input for selector: #not-file-input'
    );
    expect(getResponseText(responses.find((message) => message.id === 70))).toContain(
      `file does not exist: ${missingPath}`
    );
    expect(getResponseText(responses.find((message) => message.id === 71))).toContain(
      'pageIndex and pageId cannot be used together'
    );
  });

});
