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

interface StoreState {
  installCustomMcpServer: ReturnType<typeof vi.fn>;
  cliStatus?: Record<string, unknown> | null;
  cliStatusLoading?: boolean;
}

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

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

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

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('lucide-react', () => {
  const Icon = (props: React.SVGProps<SVGSVGElement>) => React.createElement('svg', props);
  return {
    Plus: Icon,
    Server: Icon,
    Trash2: Icon,
  };
});

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

function setNativeValue(
  element: HTMLInputElement | HTMLSelectElement,
  value: string,
  eventName: 'input' | 'change'
): 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(eventName, { bubbles: true }));
}

describe('CustomMcpServerDialog project scope', () => {
  beforeEach(() => {
    vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
    storeState.installCustomMcpServer = vi.fn().mockResolvedValue(undefined);
    storeState.cliStatus = {
      flavor: 'claude',
      installed: true,
      authLoggedIn: true,
      binaryPath: '/usr/local/bin/claude',
      launchError: null,
      providers: [],
    };
    storeState.cliStatusLoading = false;
    lookupMock.mockReset();
    lookupMock.mockResolvedValue([]);
  });

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

  it('disables non-user scopes without an active project', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);

    await act(async () => {
      root.render(
        React.createElement(CustomMcpServerDialog, {
          open: true,
          onClose: vi.fn(),
          projectPath: null,
        })
      );
      await Promise.resolve();
    });

    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('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(CustomMcpServerDialog, {
          open: true,
          onClose: vi.fn(),
          projectPath: null,
        })
      );
      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('preserves entered values when multimodel scope metadata loads after open', async () => {
    storeState.cliStatus = null;
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);

    await act(async () => {
      root.render(
        React.createElement(CustomMcpServerDialog, {
          open: true,
          onClose: vi.fn(),
          projectPath: null,
        })
      );
      await Promise.resolve();
    });

    const nameInput = host.querySelector('#custom-name') as HTMLInputElement;
    const packageInput = host.querySelector('#custom-npm') as HTMLInputElement;
    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;

    await act(async () => {
      setNativeValue(nameInput, 'late-hydration-server', 'input');
      setNativeValue(packageInput, '@example/late-hydration', 'input');
      await Promise.resolve();
    });

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

    storeState.cliStatus = { flavor: 'agent_teams_orchestrator' };
    await act(async () => {
      root.render(
        React.createElement(CustomMcpServerDialog, {
          open: true,
          onClose: vi.fn(),
          projectPath: null,
        })
      );
      await Promise.resolve();
    });

    expect((host.querySelector('#custom-name') as HTMLInputElement).value).toBe(
      'late-hydration-server'
    );
    expect((host.querySelector('#custom-npm') as HTMLInputElement).value).toBe(
      '@example/late-hydration'
    );
    expect((host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement).value).toBe(
      'global'
    );

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

  it('disables installation when the runtime declares MCP writes unavailable', async () => {
    storeState.cliStatus = {
      flavor: 'agent_teams_orchestrator',
      installed: true,
      authLoggedIn: true,
      binaryPath: '/usr/local/bin/claude-multimodel',
      launchError: null,
      providers: [
        {
          providerId: 'anthropic',
          displayName: 'Anthropic',
          supported: true,
          authenticated: true,
          authMethod: 'oauth_token',
          verificationState: 'verified',
          models: [],
          canLoginFromUi: true,
          capabilities: {
            teamLaunch: true,
            oneShot: true,
            extensions: {
              plugins: { status: 'supported', ownership: 'shared', reason: null },
              mcp: {
                status: 'read-only',
                ownership: 'shared',
                reason: 'MCP writes unavailable',
              },
              skills: { status: 'supported', ownership: 'shared', reason: null },
              apiKeys: { status: 'supported', ownership: 'shared', reason: null },
            },
          },
        },
      ],
    };
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);

    await act(async () => {
      root.render(
        React.createElement(CustomMcpServerDialog, {
          open: true,
          onClose: vi.fn(),
          projectPath: null,
        })
      );
      await Promise.resolve();
    });

    expect(host.textContent).toContain('MCP writes unavailable');
    const installButton = Array.from(host.querySelectorAll('button')).find(
      (button) => button.textContent?.includes('安装')
    ) as HTMLButtonElement | undefined;
    expect(installButton).toBeDefined();
    expect(installButton?.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);

    await act(async () => {
      root.render(
        React.createElement(CustomMcpServerDialog, {
          open: true,
          onClose: vi.fn(),
          projectPath: '/tmp/custom-mcp-project',
        })
      );
      await Promise.resolve();
    });

    const addEnvButton = Array.from(host.querySelectorAll('button')).find((button) =>
      button.textContent?.includes('添加')
    ) as HTMLButtonElement;
    await act(async () => {
      addEnvButton.click();
      await Promise.resolve();
    });

    const envKeyInput = host.querySelector(
      'input[placeholder="ENV_VAR_NAME"]'
    ) as HTMLInputElement;
    await act(async () => {
      setNativeValue(envKeyInput, 'CONTEXT7_API_KEY', 'input');
      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 () => {
      setNativeValue(scopeSelect, 'project', 'change');
      await Promise.resolve();
      await Promise.resolve();
    });

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

    await act(async () => {
      setNativeValue(scopeSelect, 'user', 'change');
      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);
    lookupMock
      .mockResolvedValueOnce([])
      .mockResolvedValueOnce([{ envVarName: 'CONTEXT7_API_KEY', value: 'project-secret' }])
      .mockResolvedValueOnce([]);

    await act(async () => {
      root.render(
        React.createElement(CustomMcpServerDialog, {
          open: true,
          onClose: vi.fn(),
          projectPath: '/tmp/custom-mcp-project',
        })
      );
      await Promise.resolve();
    });

    const addEnvButton = Array.from(host.querySelectorAll('button')).find((button) =>
      button.textContent?.includes('添加')
    ) as HTMLButtonElement;
    await act(async () => {
      addEnvButton.click();
      await Promise.resolve();
    });

    const envKeyInput = host.querySelector(
      'input[placeholder="ENV_VAR_NAME"]'
    ) as HTMLInputElement;
    const envValueInput = host.querySelector(
      'input[placeholder="value"]'
    ) as HTMLInputElement;
    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;

    await act(async () => {
      setNativeValue(envKeyInput, 'CONTEXT7_API_KEY', 'input');
      await Promise.resolve();
      await Promise.resolve();
    });

    await act(async () => {
      setNativeValue(scopeSelect, 'project', 'change');
      await Promise.resolve();
      await Promise.resolve();
    });

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

    await act(async () => {
      setNativeValue(scopeSelect, 'user', 'change');
      await Promise.resolve();
      await Promise.resolve();
    });

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

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

  it('passes projectPath for project-scoped custom installs', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const onClose = vi.fn();
    const projectPath = '/tmp/custom-mcp-project';

    await act(async () => {
      root.render(
        React.createElement(CustomMcpServerDialog, {
          open: true,
          onClose,
          projectPath,
        })
      );
      await Promise.resolve();
    });

    const nameInput = host.querySelector('#custom-name') as HTMLInputElement;
    const packageInput = host.querySelector('#custom-npm') as HTMLInputElement;
    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;

    await act(async () => {
      setNativeValue(nameInput, 'custom-context7', 'input');
      setNativeValue(packageInput, '@upstash/context7-mcp', 'input');
      setNativeValue(scopeSelect, 'project', 'change');
      await Promise.resolve();
    });

    const installButton = Array.from(host.querySelectorAll('button')).find(
      (button) => button.textContent?.includes('安装')
    ) as HTMLButtonElement;
    expect(installButton.disabled).toBe(false);

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

    expect(storeState.installCustomMcpServer).toHaveBeenCalledWith(
      expect.objectContaining({
        serverName: 'custom-context7',
        scope: 'project',
        projectPath,
      })
    );
    expect(onClose).toHaveBeenCalledTimes(1);

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

  it('passes projectPath for local-scoped custom installs', async () => {
    const host = document.createElement('div');
    document.body.appendChild(host);
    const root = createRoot(host);
    const onClose = vi.fn();
    const projectPath = '/tmp/custom-mcp-project';

    await act(async () => {
      root.render(
        React.createElement(CustomMcpServerDialog, {
          open: true,
          onClose,
          projectPath,
        })
      );
      await Promise.resolve();
    });

    const nameInput = host.querySelector('#custom-name') as HTMLInputElement;
    const packageInput = host.querySelector('#custom-npm') as HTMLInputElement;
    const scopeSelect = host.querySelector('[data-testid="scope-select"]') as HTMLSelectElement;

    await act(async () => {
      setNativeValue(nameInput, 'local-context7', 'input');
      setNativeValue(packageInput, '@upstash/context7-mcp', 'input');
      setNativeValue(scopeSelect, 'local', 'change');
      await Promise.resolve();
    });

    const installButton = Array.from(host.querySelectorAll('button')).find(
      (button) => button.textContent?.includes('安装')
    ) as HTMLButtonElement;
    expect(installButton.disabled).toBe(false);

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

    expect(storeState.installCustomMcpServer).toHaveBeenCalledWith(
      expect.objectContaining({
        serverName: 'local-context7',
        scope: 'local',
        projectPath,
      })
    );
    expect(onClose).toHaveBeenCalledTimes(1);

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