import React, { act } from 'react';
import { createRoot } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { getMcpOperationKey } from '@shared/utils/extensionNormalizers';
import type { InstalledMcpEntry, McpCatalogItem } from '@shared/types/extensions';

interface StoreState {
  mcpInstallProgress: Record<string, string>;
  installMcpServer: ReturnType<typeof vi.fn>;
  uninstallMcpServer: ReturnType<typeof vi.fn>;
  installErrors: Record<string, string>;
  mcpGitHubStars: Record<string, number>;
  cliStatus?: { flavor: 'claude' | 'agent_teams_orchestrator' } | null;
}

const storeState = {} as StoreState;
const lookupMock = vi.fn();

vi.mock('@renderer/store', () => ({
  useStore: (selector: (state: StoreState) => unknown) => selector(storeState),
}));

vi.mock('@renderer/api', () => ({
  api: {
    openExternal: vi.fn(),
    apiKeys: {
      lookup: (...args: unknown[]) => lookupMock(...args),
    },
  },
}));

vi.mock('@renderer/components/ui/badge', () => ({
  Badge: ({ children }: React.PropsWithChildren) => React.createElement('span', null, children),
}));

vi.mock('@renderer/components/ui/button', () => ({
  Button: ({
    children,
    onClick,
    type = 'button',
    disabled,
  }: React.PropsWithChildren<{
    onClick?: () => void;
    type?: 'button' | 'submit' | 'reset';
    disabled?: boolean;
  }>) =>
    React.createElement(
      'button',
      {
        type,
        disabled,
        onClick,
      },
      children
    ),
}));

vi.mock('@renderer/components/ui/dialog', () => ({
  Dialog: ({ open, children }: React.PropsWithChildren<{ open: boolean }>) =>
    open ? React.createElement('div', null, children) : null,
  DialogContent: ({ children }: React.PropsWithChildren) => React.createElement('div', null, children),
  DialogHeader: ({ children }: React.PropsWithChildren) => React.createElement('div', null, children),
  DialogTitle: ({ children }: React.PropsWithChildren) => React.createElement('h2', null, children),
  DialogDescription: ({ children }: React.PropsWithChildren) =>
    React.createElement('p', null, children),
}));

vi.mock('@renderer/components/ui/input', () => ({
  Input: (props: React.InputHTMLAttributes<HTMLInputElement>) =>
    React.createElement('input', props),
}));

vi.mock('@renderer/components/ui/label', () => ({
  Label: ({ children }: React.PropsWithChildren) => React.createElement('label', null, children),
}));

vi.mock('@renderer/components/ui/select', () => ({
  Select: ({
    children,
    value,
    onValueChange,
  }: React.PropsWithChildren<{ value: string; onValueChange: (value: string) => void }>) =>
    React.createElement(
      'select',
      {
        'data-testid': 'scope-select',
        value,
        onChange: (event: React.ChangeEvent<HTMLSelectElement>) => onValueChange(event.target.value),
      },
      children
    ),
  SelectTrigger: ({ children }: React.PropsWithChildren) =>
    React.createElement(React.Fragment, null, children),
  SelectValue: () => null,
  SelectContent: ({ children }: React.PropsWithChildren) =>
    React.createElement(React.Fragment, null, children),
  SelectItem: ({
    children,
    value,
    disabled,
  }: React.PropsWithChildren<{ value: string; disabled?: boolean }>) =>
    React.createElement('option', { value, disabled }, children),
}));

vi.mock('@renderer/components/extensions/common/InstallButton', () => ({
  InstallButton: ({
    isInstalled,
    state,
    errorMessage,
    onInstall,
    onUninstall,
  }: {
    isInstalled: boolean;
    state?: string;
    errorMessage?: string;
    onInstall: () => void;
    onUninstall: () => void;
  }) =>
    React.createElement(
      'button',
      {
        type: 'button',
        'data-testid': 'install-button',
        'data-state': state,
        'data-error': errorMessage,
        onClick: () => (isInstalled ? onUninstall() : onInstall()),
      },
      isInstalled ? 'Uninstall' : 'Install'
    ),
}));

vi.mock('@renderer/components/extensions/common/SourceBadge', () => ({
  SourceBadge: ({ source }: { source: string }) => React.createElement('span', null, source),
}));

vi.mock('lucide-react', () => {
  const Icon = (props: React.SVGProps<SVGSVGElement>) => React.createElement('svg', props);
  return {
    ExternalLink: Icon,
    Lock: Icon,
    Plus: Icon,
    Star: Icon,
    Trash2: Icon,
    Wrench: Icon,
  };
});

import { McpServerDetailDialog } from '@renderer/components/extensions/mcp/McpServerDetailDialog';

function makeServer(): McpCatalogItem {
  return {
    id: 'io.github.upstash/context7',
    name: 'Context7',
    description: 'Docs server',
    source: 'official',
    installSpec: {
      type: 'stdio',
      npmPackage: '@upstash/context7-mcp',
    },
    envVars: [],
    tools: [],
    requiresAuth: false,
    authHeaders: [],
  };
}

function setInputValue(element: HTMLInputElement | HTMLSelectElement, value: string): void {
  const prototype =
    element instanceof HTMLSelectElement ? HTMLSelectElement.prototype : HTMLInputElement.prototype;
  const descriptor = Object.getOwnPropertyDescriptor(prototype, 'value');
  descriptor?.set?.call(element, value);
  element.dispatchEvent(new Event('input', { bubbles: true }));
  element.dispatchEvent(new Event('change', { bubbles: true }));
}

describe('McpServerDetailDialog installed entry handling', () => {
  beforeEach(() => {
    vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
    storeState.mcpInstallProgress = {};
    storeState.installMcpServer = vi.fn();
    storeState.uninstallMcpServer = vi.fn();
    storeState.installErrors = {};
    storeState.mcpGitHubStars = {};
    storeState.cliStatus = null;
    lookupMock.mockReset();
    lookupMock.mockResolvedValue([]);
  });

  afterEach(() => {
    document.body.innerHTML = '';
    vi.unstubAllGlobals();
  });

  it('uninstalls using the real installed server name and scope', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const installedEntry: InstalledMcpEntry = {
      name: 'context7-local',
      scope: 'local',
    };

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server: makeServer(),
          isInstalled: true,
          installedEntry,
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: '/tmp/project',
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
    });

    const serverNameInput = host.querySelector('#server-name') as HTMLInputElement;
    expect(serverNameInput).not.toBeNull();
    expect(serverNameInput.value).toBe('context7-local');
    expect(serverNameInput.disabled).toBe(true);

    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
    expect(scopeSelect.value).toBe('local');

    const uninstallButton = host.querySelector('[data-testid="install-button"]') as HTMLButtonElement;
    await act(async () => {
      uninstallButton.click();
      await Promise.resolve();
    });

    expect(storeState.uninstallMcpServer).toHaveBeenCalledWith(
      'io.github.upstash/context7',
      'context7-local',
      'local',
      '/tmp/project'
    );

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('looks up saved API keys only once per dialog open', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const server = makeServer();
    server.envVars = [{ name: 'CONTEXT7_API_KEY', isSecret: true }];
    lookupMock.mockResolvedValue([
      {
        envVarName: 'CONTEXT7_API_KEY',
        value: 'secret',
      },
    ]);

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server,
          isInstalled: false,
          installedEntry: null,
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: null,
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
      await Promise.resolve();
    });

    expect(lookupMock).toHaveBeenCalledTimes(1);
    expect(lookupMock).toHaveBeenCalledWith(['CONTEXT7_API_KEY'], undefined);
    const projectOption = host.querySelector('option[value="project"]') as HTMLOptionElement;
    const localOption = host.querySelector('option[value="local"]') as HTMLOptionElement;
    expect(projectOption.disabled).toBe(true);
    expect(localOption.disabled).toBe(true);

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('looks up project-scoped API keys only when project scope is selected', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const server = makeServer();
    server.envVars = [{ name: 'CONTEXT7_API_KEY', isSecret: true }];

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server,
          isInstalled: false,
          installedEntry: null,
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: '/tmp/project-context7',
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
      await Promise.resolve();
    });

    expect(lookupMock).toHaveBeenCalledWith(['CONTEXT7_API_KEY'], undefined);

    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
    await act(async () => {
      scopeSelect.value = 'project';
      scopeSelect.dispatchEvent(new Event('change', { bubbles: true }));
      await Promise.resolve();
      await Promise.resolve();
    });

    expect(lookupMock).toHaveBeenLastCalledWith(['CONTEXT7_API_KEY'], '/tmp/project-context7');

    await act(async () => {
      scopeSelect.value = 'user';
      scopeSelect.dispatchEvent(new Event('change', { bubbles: true }));
      await Promise.resolve();
      await Promise.resolve();
    });

    expect(lookupMock).toHaveBeenLastCalledWith(['CONTEXT7_API_KEY'], undefined);

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('clears stale project auto-filled values when switching back to user scope', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const server = makeServer();
    server.envVars = [{ name: 'CONTEXT7_API_KEY', isSecret: true }];
    lookupMock
      .mockResolvedValueOnce([])
      .mockResolvedValueOnce([{ envVarName: 'CONTEXT7_API_KEY', value: 'project-secret' }])
      .mockResolvedValueOnce([]);

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server,
          isInstalled: false,
          installedEntry: null,
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: '/tmp/project-context7',
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
      await Promise.resolve();
    });

    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
    const envValueInput = host.querySelector('input[type="password"]') as HTMLInputElement;

    await act(async () => {
      scopeSelect.value = 'project';
      scopeSelect.dispatchEvent(new Event('change', { bubbles: true }));
      await Promise.resolve();
      await Promise.resolve();
    });

    expect(envValueInput.value).toBe('project-secret');

    await act(async () => {
      scopeSelect.value = 'user';
      scopeSelect.dispatchEvent(new Event('change', { bubbles: true }));
      await Promise.resolve();
      await Promise.resolve();
    });

    expect(envValueInput.value).toBe('');

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('defaults to global scope in multimodel mode', async () => {
    storeState.cliStatus = { flavor: 'agent_teams_orchestrator' };
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server: makeServer(),
          isInstalled: false,
          installedEntry: null,
          installedEntries: [],
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: null,
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
    });

    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
    expect(scopeSelect.value).toBe('global');

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('uses a runtime-aware status label in multimodel mode', async () => {
    storeState.cliStatus = { flavor: 'agent_teams_orchestrator' };
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const installedEntry: InstalledMcpEntry = {
      name: 'context7-global',
      scope: 'global',
    };

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server: makeServer(),
          isInstalled: true,
          installedEntry,
          installedEntries: [installedEntry],
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: null,
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
    });

    expect(host.textContent).toContain('运行时状态');
    expect(host.textContent).not.toContain('Claude 状态');

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('preserves edited fields when multimodel scope metadata loads after open', async () => {
    storeState.cliStatus = null;
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const server = makeServer();
    server.envVars = [{ name: 'CONTEXT7_API_KEY', isSecret: true }];

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server,
          isInstalled: false,
          installedEntry: null,
          installedEntries: [],
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: null,
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
      await Promise.resolve();
    });

    const serverNameInput = host.querySelector('#server-name') as HTMLInputElement;
    const envValueInput = host.querySelector('input[type="password"]') as HTMLInputElement;
    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;

    await act(async () => {
      setInputValue(serverNameInput, 'late-hydration-context7');
      setInputValue(envValueInput, 'secret');
      await Promise.resolve();
    });

    expect(scopeSelect.value).toBe('user');

    storeState.cliStatus = { flavor: 'agent_teams_orchestrator' };
    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server,
          isInstalled: false,
          installedEntry: null,
          installedEntries: [],
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: null,
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
      await Promise.resolve();
    });

    expect((host.querySelector('#server-name') as HTMLInputElement).value).toBe(
      'late-hydration-context7'
    );
    expect((host.querySelector('input[type="password"]') as HTMLInputElement).value).toBe(
      'secret'
    );
    expect((host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement).value).toBe(
      'global'
    );

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('passes project path for project-scoped installs and uninstalls', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const projectPath = '/tmp/project-context7';
    const installedEntry: InstalledMcpEntry = {
      name: 'context7-project',
      scope: 'project',
    };

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server: makeServer(),
          isInstalled: true,
          installedEntry,
          installedEntries: [installedEntry],
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath,
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
    });

    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
    expect(scopeSelect.value).toBe('project');

    const uninstallButton = host.querySelector('[data-testid="install-button"]') as HTMLButtonElement;
    await act(async () => {
      uninstallButton.click();
      await Promise.resolve();
    });

    expect(storeState.uninstallMcpServer).toHaveBeenCalledWith(
      'io.github.upstash/context7',
      'context7-project',
      'project',
      projectPath
    );

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server: makeServer(),
          isInstalled: false,
          installedEntry: null,
          installedEntries: [],
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath,
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
    });

    const installScopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
    await act(async () => {
      installScopeSelect.value = 'project';
      installScopeSelect.dispatchEvent(new Event('change', { bubbles: true }));
      await Promise.resolve();
    });

    const installButton = host.querySelector('[data-testid="install-button"]') as HTMLButtonElement;
    await act(async () => {
      installButton.click();
      await Promise.resolve();
    });

    expect(storeState.installMcpServer).toHaveBeenCalledWith(
      expect.objectContaining({
        registryId: 'io.github.upstash/context7',
        scope: 'project',
        projectPath,
      })
    );

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('passes project path for local-scoped installs and uninstalls', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const projectPath = '/tmp/local-context7';
    const installedEntry: InstalledMcpEntry = {
      name: 'context7-local',
      scope: 'local',
    };

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server: makeServer(),
          isInstalled: true,
          installedEntry,
          installedEntries: [installedEntry],
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath,
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
    });

    const uninstallButton = host.querySelector('[data-testid="install-button"]') as HTMLButtonElement;
    await act(async () => {
      uninstallButton.click();
      await Promise.resolve();
    });

    expect(storeState.uninstallMcpServer).toHaveBeenCalledWith(
      'io.github.upstash/context7',
      'context7-local',
      'local',
      projectPath
    );

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server: makeServer(),
          isInstalled: false,
          installedEntry: null,
          installedEntries: [],
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath,
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
    });

    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
    await act(async () => {
      scopeSelect.value = 'local';
      scopeSelect.dispatchEvent(new Event('change', { bubbles: true }));
      await Promise.resolve();
    });

    const installButton = host.querySelector('[data-testid="install-button"]') as HTMLButtonElement;
    await act(async () => {
      installButton.click();
      await Promise.resolve();
    });

    expect(storeState.installMcpServer).toHaveBeenCalledWith(
      expect.objectContaining({
        registryId: 'io.github.upstash/context7',
        scope: 'local',
        projectPath,
      })
    );

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('uses selected scope instead of aggregated installed state', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const installedEntry: InstalledMcpEntry = {
      name: 'context7',
      scope: 'user',
    };

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server: makeServer(),
          isInstalled: true,
          installedEntry,
          installedEntries: [installedEntry],
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: '/tmp/project',
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
    });

    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
    await act(async () => {
      scopeSelect.value = 'project';
      scopeSelect.dispatchEvent(new Event('change', { bubbles: true }));
      await Promise.resolve();
    });

    const actionButton = host.querySelector('[data-testid="install-button"]') as HTMLButtonElement;
    expect(actionButton.textContent).toBe('Install');

    await act(async () => {
      actionButton.click();
      await Promise.resolve();
    });

    expect(storeState.installMcpServer).toHaveBeenCalledWith(
      expect.objectContaining({
        registryId: 'io.github.upstash/context7',
        scope: 'project',
        projectPath: '/tmp/project',
      })
    );
    expect(storeState.uninstallMcpServer).not.toHaveBeenCalled();

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('defaults to the highest-precedence installed scope', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const installedEntries: InstalledMcpEntry[] = [
      { name: 'context7', scope: 'user' },
      { name: 'context7-shared', scope: 'project' },
    ];

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server: makeServer(),
          isInstalled: true,
          installedEntry: installedEntries[0],
          installedEntries,
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: '/tmp/project',
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
    });

    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
    const serverNameInput = host.querySelector('#server-name') as HTMLInputElement;

    expect(scopeSelect.value).toBe('project');
    expect(serverNameInput.value).toBe('context7-shared');

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });

  it('reads install state from the selected scope operation key', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    storeState.mcpInstallProgress = {
      [getMcpOperationKey('io.github.upstash/context7', 'user')]: 'success',
      [getMcpOperationKey('io.github.upstash/context7', 'project', '/tmp/project')]: 'error',
    };
    storeState.installErrors = {
      [getMcpOperationKey('io.github.upstash/context7', 'project', '/tmp/project')]:
        'Project failed',
    };

    await act(async () => {
      root.render(
        React.createElement(McpServerDetailDialog, {
          server: makeServer(),
          isInstalled: false,
          installedEntry: null,
          installedEntries: [],
          diagnostic: null,
          diagnosticsLoading: false,
          projectPath: '/tmp/project',
          open: true,
          onClose: vi.fn(),
        })
      );
      await Promise.resolve();
    });

    const installButton = host.querySelector('[data-testid="install-button"]') as HTMLButtonElement;
    expect(installButton.dataset.state).toBe('success');
    expect(installButton.dataset.error ?? '').toBe('');

    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;
    await act(async () => {
      scopeSelect.value = 'project';
      scopeSelect.dispatchEvent(new Event('change', { bubbles: true }));
      await Promise.resolve();
    });

    expect(installButton.dataset.state).toBe('error');
    expect(installButton.dataset.error).toBe('Project failed');

    await act(async () => {
      root.unmount();
      await Promise.resolve();
    });
  });
});
