import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { forwardRef, useImperativeHandle } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useHasPermissions } from "@/lib/auth/auth.query";
import {
  useEnterpriseFeature,
  useFeature,
  useSmallTeamTier,
} from "@/lib/config/config.query";
import { useAppName } from "@/lib/hooks/use-app-name";
import { AgentDialog } from "./agent-dialog";

global.ResizeObserver = class ResizeObserver {
  observe() {}
  unobserve() {}
  disconnect() {}
} as typeof ResizeObserver;

const {
  pendingSaveChanges,
  useAvailableLlmProviderApiKeysMock,
  useAgentDelegationsMock,
  useInternalAgentsMock,
  useLlmModelsByProviderMock,
  useProfileMock,
  useSyncAgentDelegationsMock,
  useUpdateProfileMock,
} = vi.hoisted(() => ({
  pendingSaveChanges: vi.fn(
    () => new Promise<void>((resolve) => setTimeout(resolve, 50)),
  ),
  useInternalAgentsMock: vi.fn((): { data: unknown[] } => ({ data: [] })),
  useProfileMock: vi.fn(
    (): { data: unknown | null; refetch: ReturnType<typeof vi.fn> } => ({
      data: null,
      refetch: vi.fn(),
    }),
  ),
  useAvailableLlmProviderApiKeysMock: vi.fn(() => ({ data: [] })),
  useLlmModelsByProviderMock: vi.fn(() => ({ modelsByProvider: {} })),
  useUpdateProfileMock: vi.fn(() => ({
    mutateAsync: vi.fn(),
    isPending: false,
  })),
  useAgentDelegationsMock: vi.fn(
    (): { data: unknown[]; isFetched: boolean } => ({
      data: [],
      isFetched: true,
    }),
  ),
  useSyncAgentDelegationsMock: vi.fn(() => ({
    mutateAsync: vi.fn(),
    isPending: false,
  })),
}));

vi.mock("@tanstack/react-query", async () => {
  const actual = await vi.importActual("@tanstack/react-query");
  return {
    ...actual,
    useQuery: () => ({ data: [] }),
  };
});

vi.mock("@/lib/agent.query", () => ({
  useCreateProfile: () => ({
    mutateAsync: vi.fn(),
    isPending: false,
  }),
  useDeleteProfile: () => ({
    mutateAsync: vi.fn(),
    isPending: false,
  }),
  useInternalAgents: useInternalAgentsMock,
  useProfile: useProfileMock,
  useUpdateProfile: useUpdateProfileMock,
}));

vi.mock("@/lib/agent-tools.query", () => ({
  useAgentDelegations: useAgentDelegationsMock,
  useSyncAgentDelegations: useSyncAgentDelegationsMock,
}));

vi.mock("@/lib/auth/auth.query");

vi.mock("@/lib/chat/chat.query", () => ({
  useChatProfileMcpTools: () => ({ data: [], isLoading: false }),
}));

vi.mock("@/lib/config/config.query");

vi.mock("@/lib/knowledge/connector.query", () => ({
  useConnectors: () => ({ data: [] }),
}));

vi.mock("@/lib/knowledge/knowledge-base.query", () => ({
  useKnowledgeBases: () => ({ data: [] }),
  useIsKnowledgeBaseConfigured: () => true,
}));

vi.mock("@/lib/llm-models.query", () => ({
  useLlmModelsByProvider: useLlmModelsByProviderMock,
}));

vi.mock("@/lib/llm-provider-api-keys.query", () => ({
  useAvailableLlmProviderApiKeys: useAvailableLlmProviderApiKeysMock,
}));

vi.mock("@/lib/hooks/use-app-name");

vi.mock("@/lib/docs/docs", () => ({
  getFrontendDocsUrl: () => "/docs",
}));

vi.mock("@/lib/utils", () => ({
  cn: (...classes: Array<string | false | null | undefined>) =>
    classes.filter(Boolean).join(" "),
}));

vi.mock("@/lib/config/config", () => ({
  default: {
    enterpriseFeatures: {
      core: false,
    },
  },
}));

vi.mock("@/components/agent-tools-editor", () => ({
  AgentToolsEditor: forwardRef((_props, ref) => {
    useImperativeHandle(ref, () => ({
      saveChanges: pendingSaveChanges,
    }));

    return <div>Mock Tools Editor</div>;
  }),
}));

vi.mock("@/components/agent-tool-exclusions-editor", () => ({
  AgentToolExclusionsEditor: forwardRef((_props, ref) => {
    useImperativeHandle(ref, () => ({
      saveChanges: vi.fn(),
    }));

    return <div>Mock Tool Exclusions Editor</div>;
  }),
}));

vi.mock("@/components/agent-labels", () => ({
  ProfileLabels: () => null,
}));

vi.mock("@/components/agent-badge", () => ({
  AgentBadge: () => null,
}));

vi.mock("@/components/agent-icon-picker", () => ({
  AgentIconPicker: () => null,
}));

vi.mock("@/components/chat/model-selector", () => ({
  ModelSelector: () => null,
}));

vi.mock("@/components/external-docs-link", () => ({
  ExternalDocsLink: () => null,
}));

vi.mock("@/components/permission-requirement-hint", () => ({
  PermissionRequirementHint: () => null,
  formatPermissionRequirement: () => "",
}));

vi.mock("@/components/system-prompt-editor", () => ({
  SystemPromptEditor: () => null,
}));

vi.mock("@/components/visibility-selector", () => ({
  VisibilitySelector: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
}));

vi.mock("@/components/environment-selector", () => ({
  EnvironmentSelector: () => <div />,
}));

vi.mock("@/components/ui/alert", () => ({
  Alert: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  AlertDescription: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
}));

vi.mock("@/components/ui/assignment-combobox", () => ({
  AssignmentCombobox: () => null,
}));

vi.mock("@/components/ui/badge", () => ({
  Badge: ({ children }: { children?: React.ReactNode }) => (
    <span>{children}</span>
  ),
}));

vi.mock("@/components/ui/button", () => ({
  Button: ({
    children,
    ...props
  }: React.ButtonHTMLAttributes<HTMLButtonElement>) => (
    <button {...props}>{children}</button>
  ),
}));

vi.mock("@/components/ui/collapsible", () => ({
  Collapsible: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  CollapsibleContent: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  CollapsibleTrigger: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
}));

vi.mock("@/components/ui/command", () => ({
  Command: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  CommandEmpty: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  CommandGroup: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  CommandInput: () => null,
  CommandItem: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  CommandList: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
}));

vi.mock("@/components/ui/dialog", () => ({
  Dialog: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  DialogContent: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  DialogDescription: ({ children }: { children?: React.ReactNode }) => (
    <p>{children}</p>
  ),
  DialogForm: ({
    children,
    onSubmit,
  }: {
    children?: React.ReactNode;
    onSubmit?: React.FormEventHandler<HTMLFormElement>;
  }) => <form onSubmit={onSubmit}>{children}</form>,
  DialogHeader: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  DialogStickyFooter: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  DialogTitle: ({ children }: { children?: React.ReactNode }) => (
    <h1>{children}</h1>
  ),
}));

vi.mock("@/components/ui/expandable-text", () => ({
  ExpandableText: ({ text }: { text: string }) => <span>{text}</span>,
}));

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

vi.mock("@/components/ui/label", () => ({
  Label: ({ children }: React.LabelHTMLAttributes<HTMLLabelElement>) => (
    <span>{children}</span>
  ),
}));

vi.mock("@/components/ui/multi-select-combobox", () => ({
  MultiSelectCombobox: () => null,
}));

vi.mock("@/components/ui/overlapped-icons", () => ({
  OverlappedIcons: () => null,
}));

vi.mock("@/components/ui/popover", () => ({
  Popover: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  PopoverContent: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  PopoverTrigger: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
}));

vi.mock("@/components/ui/select", () => ({
  Select: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  SelectContent: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  SelectItem: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  SelectTrigger: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  SelectValue: () => null,
}));

vi.mock("@/components/ui/switch", () => ({
  Switch: () => null,
}));

vi.mock("@/components/ui/textarea", () => ({
  Textarea: (props: React.TextareaHTMLAttributes<HTMLTextAreaElement>) => (
    <textarea {...props} />
  ),
}));

vi.mock("@/components/ui/tooltip", () => ({
  Tooltip: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  TooltipContent: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  TooltipProvider: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
  TooltipTrigger: ({ children }: { children?: React.ReactNode }) => (
    <div>{children}</div>
  ),
}));

beforeEach(() => {
  vi.mocked(useFeature).mockReturnValue(
    false as unknown as ReturnType<typeof useFeature>,
  );
  vi.mocked(useEnterpriseFeature).mockReturnValue(false);
  vi.mocked(useSmallTeamTier).mockReturnValue(undefined);
  vi.mocked(useAppName).mockReturnValue("Archestra");
});

const baseAgent = {
  id: "00000000-0000-4000-8000-000000000001",
  organizationId: "00000000-0000-4000-8000-000000000010",
  name: "Existing Agent",
  builtIn: false,
  icon: null,
  description: null,
  systemPrompt: null,
  agentType: "agent" as const,
  toolExposureMode: "full" as const,
  accessAllTools: false,
  scope: "personal" as const,
  isDefault: false,
  isPersonalGateway: false,
  isPersonalProxy: false,
  teams: [],
  tools: [],
  labels: [],
  authorId: "00000000-0000-4000-8000-000000000020",
  authorName: "Test User",
  createdAt: new Date().toISOString(),
  updatedAt: new Date().toISOString(),
  deletedAt: null,
  knowledgeBaseIds: [],
  connectorIds: [],
  suggestedPrompts: [],
  llmApiKeyId: null,
  llmModel: null,
  modelId: null,
  considerContextUntrusted: false,
  identityProviderId: null,
  environmentId: null,
  builtInAgentConfig: null,
  passthroughHeaders: null,
  incomingEmailEnabled: false,
  incomingEmailSecurityMode: "public" as const,
  incomingEmailAllowedDomain: null,
  slug: null,
};

const targetAgent = {
  ...baseAgent,
  id: "00000000-0000-4000-8000-000000000002",
  name: "Target Agent",
};

describe("AgentDialog delegation state", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(useHasPermissions).mockImplementation(
      () => ({ data: true }) as unknown as ReturnType<typeof useHasPermissions>,
    );
    useProfileMock.mockReturnValue({ data: null, refetch: vi.fn() });
    useInternalAgentsMock.mockReturnValue({ data: [targetAgent] });
    useAgentDelegationsMock.mockReturnValue({
      data: [targetAgent],
      isFetched: true,
    });
  });

  it("keeps selected subagents when fresh agent data refetches", async () => {
    const { rerender } = render(
      <AgentDialog
        open={true}
        onOpenChange={vi.fn()}
        agentType="agent"
        agent={baseAgent}
      />,
    );

    await screen.findByText("Subagents (1)");

    useProfileMock.mockReturnValue({
      data: { ...baseAgent, description: "Refetched description" },
      refetch: vi.fn(),
    });

    rerender(
      <AgentDialog
        open={true}
        onOpenChange={vi.fn()}
        agentType="agent"
        agent={baseAgent}
      />,
    );

    await waitFor(() => {
      expect(screen.getByText("Subagents (1)")).toBeInTheDocument();
    });
  });
});

describe.skip("AgentDialog", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    vi.mocked(useHasPermissions).mockImplementation(
      () => ({ data: true }) as unknown as ReturnType<typeof useHasPermissions>,
    );
  });

  it("does not eagerly enable agent-only queries for a closed MCP gateway dialog", () => {
    render(
      <AgentDialog
        open={false}
        onOpenChange={vi.fn()}
        agentType="mcp_gateway"
      />,
    );

    expect(useInternalAgentsMock).toHaveBeenCalledWith({ enabled: false });
    expect(useAvailableLlmProviderApiKeysMock).toHaveBeenCalledWith({
      includeKeyId: undefined,
      enabled: false,
    });
    expect(useLlmModelsByProviderMock).toHaveBeenCalledWith({
      enabled: false,
    });
  });

  it("disables Update immediately while save starts", async () => {
    const user = userEvent.setup();

    render(
      <AgentDialog
        open={true}
        onOpenChange={vi.fn()}
        agentType="agent"
        agent={{
          id: "agent-1",
          organizationId: "org-1",
          name: "Existing Agent",
          builtIn: false,
          icon: null,
          description: null,
          systemPrompt: null,
          agentType: "agent",
          toolExposureMode: "full",
          accessAllTools: false,
          scope: "personal",
          isDefault: false,
          isPersonalGateway: false,
          isPersonalProxy: false,
          teams: [],
          tools: [],
          labels: [],
          authorId: "user-1",
          deletedAt: null,
          authorName: "Test User",
          createdAt: new Date().toISOString(),
          updatedAt: new Date().toISOString(),
          knowledgeBaseIds: [],
          connectorIds: [],
          suggestedPrompts: [],
          llmApiKeyId: null,
          llmModel: null,
          modelId: null,
          considerContextUntrusted: false,
          identityProviderId: null,
          environmentId: null,
          builtInAgentConfig: null,
          passthroughHeaders: null,
          incomingEmailEnabled: false,
          incomingEmailSecurityMode: "public",
          incomingEmailAllowedDomain: null,
          slug: null,
        }}
      />,
    );

    const updateButton = screen.getByRole("button", { name: /update/i });
    expect(updateButton).not.toBeDisabled();

    await user.click(updateButton);

    await waitFor(() => {
      expect(pendingSaveChanges).toHaveBeenCalledOnce();
      expect(screen.getByRole("button", { name: /update/i })).toBeDisabled();
    });
  });

  it("does not enable LLM queries when the user lacks LLM read permissions", () => {
    vi.mocked(useHasPermissions).mockImplementation(((...args: unknown[]) => {
      const permissions = (args[0] ?? {}) as Record<string, unknown>;
      if ("llmProviderApiKey" in permissions || "llmModel" in permissions) {
        return { data: false };
      }
      return { data: true };
    }) as unknown as typeof useHasPermissions);

    render(
      <AgentDialog open={true} onOpenChange={vi.fn()} agentType="agent" />,
    );

    expect(useAvailableLlmProviderApiKeysMock).toHaveBeenCalledWith({
      includeKeyId: undefined,
      enabled: false,
    });
    expect(useLlmModelsByProviderMock).toHaveBeenCalledWith({
      enabled: false,
    });
  });

  it("shows org default model message when the user cannot read keys or models", () => {
    vi.mocked(useHasPermissions).mockImplementation(((...args: unknown[]) => {
      const permissions = (args[0] ?? {}) as Record<string, unknown>;
      if ("llmProviderApiKey" in permissions || "llmModel" in permissions) {
        return { data: false };
      }
      return { data: true };
    }) as unknown as typeof useHasPermissions);

    render(
      <AgentDialog open={true} onOpenChange={vi.fn()} agentType="agent" />,
    );

    expect(
      screen.getByText(
        /you do not have permission to view llm api keys or models/i,
      ),
    ).toBeInTheDocument();
    expect(
      screen.getByText(/this agent will use the organization's default model/i),
    ).toBeInTheDocument();
  });
});
