import { render, screen, waitFor, within } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import userEvent from "@testing-library/user-event";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { createRoutesStub, Outlet } from "react-router";
import SettingsService from "#/api/settings-service/settings-service.api";
import V1ConversationService from "#/api/conversation-service/v1-conversation-service.api";
import GitService from "#/api/git-service/git-service.api";
import OptionService from "#/api/option-service/option-service.api";
import { GitRepository } from "#/types/git";
import { RepoConnector } from "#/components/features/home/repo-connector";
import { MOCK_DEFAULT_USER_SETTINGS } from "#/mocks/handlers";
import { useSelectedOrganizationStore } from "#/stores/selected-organization-store";

const renderRepoConnector = () => {
  const mockRepoSelection = vi.fn();
  const RouterStub = createRoutesStub([
    {
      Component: () => <RepoConnector onRepoSelection={mockRepoSelection} />,
      path: "/",
    },
    {
      Component: () => <div data-testid="conversation-screen" />,
      path: "/conversations/:conversationId",
    },
    {
      Component: () => <Outlet />,
      path: "/settings",
      children: [
        {
          Component: () => <div data-testid="settings-screen" />,
          path: "/settings",
        },
        {
          Component: () => <div data-testid="git-settings-screen" />,
          path: "/settings/integrations",
        },
      ],
    },
  ]);

  return render(<RouterStub />, {
    wrapper: ({ children }) => (
      <QueryClientProvider client={new QueryClient()}>
        {children}
      </QueryClientProvider>
    ),
  });
};

const MOCK_RESPOSITORIES: GitRepository[] = [
  {
    id: "1",
    full_name: "rbren/polaris",
    git_provider: "github",
    is_public: true,
    main_branch: "main",
  },
  {
    id: "2",
    full_name: "OpenHands/OpenHands",
    git_provider: "github",
    is_public: true,
    main_branch: "main",
  },
];

beforeEach(() => {
  useSelectedOrganizationStore.setState({ organizationId: "test-org-id" });
  const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
  getSettingsSpy.mockResolvedValue({
    ...MOCK_DEFAULT_USER_SETTINGS,
    provider_tokens_set: {
      github: "some-token",
      gitlab: null,
      azure_devops: null,
    },
  });
});

describe("RepoConnector", () => {
  it("should render the repository connector section", () => {
    renderRepoConnector();
    screen.getByTestId("repo-connector");
  });

  it("should render the available repositories in the dropdown", async () => {
    const retrieveUserGitRepositoriesSpy = vi.spyOn(
      GitService,
      "retrieveUserGitRepositories",
    );
    retrieveUserGitRepositoriesSpy.mockResolvedValue({
      items: MOCK_RESPOSITORIES,
      next_page_id: null,
    });

    // Mock the search function that's used by the dropdown
    vi.spyOn(GitService, "searchGitRepositories").mockResolvedValue({
      items: MOCK_RESPOSITORIES,
      next_page_id: null,
    });

    renderRepoConnector();

    // First select the provider
    const providerDropdown = await waitFor(() =>
      screen.getByTestId("git-provider-dropdown"),
    );
    await userEvent.click(providerDropdown);
    await userEvent.click(screen.getByText("GitHub"));

    // Then interact with the repository dropdown
    const repoInput = await waitFor(() =>
      screen.getByTestId("git-repo-dropdown"),
    );
    await userEvent.click(repoInput);

    // Wait for the options to be loaded and displayed
    await waitFor(() => {
      expect(screen.getByText("rbren/polaris")).toBeInTheDocument();
      expect(screen.getByText("OpenHands/OpenHands")).toBeInTheDocument();
    });
  });

  it("should only enable the launch button if a repo is selected", async () => {
    const retrieveUserGitRepositoriesSpy = vi.spyOn(
      GitService,
      "retrieveUserGitRepositories",
    );
    retrieveUserGitRepositoriesSpy.mockResolvedValue({
      items: MOCK_RESPOSITORIES,
      next_page_id: null,
    });

    renderRepoConnector();

    const launchButton = await screen.findByTestId("repo-launch-button");
    expect(launchButton).toBeDisabled();

    // Mock the repository branches API call
    vi.spyOn(GitService, "getRepositoryBranches").mockResolvedValue({
      items: [
        { name: "main", commit_sha: "123", protected: false },
        { name: "develop", commit_sha: "456", protected: false },
      ],
      next_page_id: null,
    });

    // First select the provider
    const providerDropdown = await waitFor(() =>
      screen.getByTestId("git-provider-dropdown"),
    );
    await userEvent.click(providerDropdown);
    await userEvent.click(screen.getByText("GitHub"));

    // Then select the repository
    const repoInput = await waitFor(() =>
      screen.getByTestId("git-repo-dropdown"),
    );

    await userEvent.click(repoInput);

    // Wait for the options to be loaded and displayed
    await waitFor(() => {
      expect(screen.getByText("rbren/polaris")).toBeInTheDocument();
    });
    await userEvent.click(screen.getByText("rbren/polaris"));

    // Wait for the branch to be auto-selected
    await waitFor(() => {
      const branchInput = screen.getByTestId("git-branch-dropdown-input");
      expect(branchInput).toHaveValue("main");
    });

    expect(launchButton).toBeEnabled();
  });

  it("should render the 'add github repos' link in dropdown if saas mode and github provider is set", async () => {
    const getConfiSpy = vi.spyOn(OptionService, "getConfig");
    // @ts-expect-error - only return the app_mode and github_app_slug
    getConfiSpy.mockResolvedValue({
      app_mode: "saas",
      github_app_slug: "openhands",
    });

    const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
    getSettingsSpy.mockResolvedValue({
      ...MOCK_DEFAULT_USER_SETTINGS,
      provider_tokens_set: {
        github: "some-token",
        gitlab: null,
      },
    });

    const retrieveUserGitRepositoriesSpy = vi.spyOn(
      GitService,
      "retrieveUserGitRepositories",
    );
    retrieveUserGitRepositoriesSpy.mockResolvedValue({
      items: MOCK_RESPOSITORIES,
      next_page_id: null,
    });

    renderRepoConnector();

    // First select the GitHub provider
    const providerDropdown = await waitFor(() =>
      screen.getByTestId("git-provider-dropdown"),
    );
    await userEvent.click(providerDropdown);
    await userEvent.click(screen.getByText("GitHub"));

    // Then open the repository dropdown
    const repoInput = await waitFor(() =>
      screen.getByTestId("git-repo-dropdown"),
    );
    await userEvent.click(repoInput);

    // The "Add GitHub repos" link should be in the dropdown
    await waitFor(() => {
      expect(screen.getByText("HOME$ADD_GITHUB_REPOS")).toBeInTheDocument();
    });
  });

  it("should not render the 'add github repos' link if github provider is not set", async () => {
    const getConfiSpy = vi.spyOn(OptionService, "getConfig");
    // @ts-expect-error - only return the app_mode and github_app_slug for this test
    getConfiSpy.mockResolvedValue({
      app_mode: "saas",
      github_app_slug: "openhands",
    });

    const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
    getSettingsSpy.mockResolvedValue({
      ...MOCK_DEFAULT_USER_SETTINGS,
      provider_tokens_set: {
        gitlab: "some-token",
        github: null,
      },
    });

    const retrieveUserGitRepositoriesSpy = vi.spyOn(
      GitService,
      "retrieveUserGitRepositories",
    );
    retrieveUserGitRepositoriesSpy.mockResolvedValue({
      items: MOCK_RESPOSITORIES,
      next_page_id: null,
    });

    renderRepoConnector();

    // First select the GitLab provider (not GitHub)
    const providerDropdown = await waitFor(() =>
      screen.getByTestId("git-provider-dropdown"),
    );
    await userEvent.click(providerDropdown);
    await userEvent.click(screen.getByText("GitLab"));

    // Then open the repository dropdown
    const repoInput = await waitFor(() =>
      screen.getByTestId("git-repo-dropdown"),
    );
    await userEvent.click(repoInput);

    // The "Add GitHub repos" link should NOT be in the dropdown for GitLab
    expect(screen.queryByText("HOME$ADD_GITHUB_REPOS")).not.toBeInTheDocument();
  });

  it("should not render the 'add github repos' link in dropdown if oss mode", async () => {
    const getConfiSpy = vi.spyOn(OptionService, "getConfig");
    // @ts-expect-error - only return the app_mode
    getConfiSpy.mockResolvedValue({
      app_mode: "oss",
    });

    const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
    getSettingsSpy.mockResolvedValue({
      ...MOCK_DEFAULT_USER_SETTINGS,
      provider_tokens_set: {
        github: "some-token",
        gitlab: null,
      },
    });

    const retrieveUserGitRepositoriesSpy = vi.spyOn(
      GitService,
      "retrieveUserGitRepositories",
    );
    retrieveUserGitRepositoriesSpy.mockResolvedValue({
      items: MOCK_RESPOSITORIES,
      next_page_id: null,
    });

    renderRepoConnector();

    // First select the GitHub provider
    const providerDropdown = await waitFor(() =>
      screen.getByTestId("git-provider-dropdown"),
    );
    await userEvent.click(providerDropdown);
    await userEvent.click(screen.getByText("GitHub"));

    // Then open the repository dropdown
    const repoInput = await waitFor(() =>
      screen.getByTestId("git-repo-dropdown"),
    );
    await userEvent.click(repoInput);

    // The "Add GitHub repos" link should NOT be in the dropdown for OSS mode
    expect(screen.queryByText("HOME$ADD_GITHUB_REPOS")).not.toBeInTheDocument();
  });

  it("should create a conversation and redirect with the selected repo when pressing the launch button", async () => {
    const createConversationSpy = vi
      .spyOn(V1ConversationService, "createConversation")
      .mockResolvedValue({
        id: "task-id",
        created_by_user_id: null,
        status: "READY",
        detail: null,
        app_conversation_id: "mock-conversation-id",
        sandbox_id: null,
        agent_server_url: "http://agent-server.local",
        request: {
          sandbox_id: null,
          initial_message: null,
          processors: [],
          llm_model: null,
          selected_repository: "rbren/polaris",
          selected_branch: "main",
          git_provider: "github",
          suggested_task: null,
          title: null,
          trigger: null,
          pr_number: [],
          parent_conversation_id: null,
          agent_type: "default",
        },
        created_at: new Date().toISOString(),
        updated_at: new Date().toISOString(),
      });
    const retrieveUserGitRepositoriesSpy = vi.spyOn(
      GitService,
      "retrieveUserGitRepositories",
    );
    retrieveUserGitRepositoriesSpy.mockResolvedValue({
      items: MOCK_RESPOSITORIES,
      next_page_id: null,
    });

    renderRepoConnector();

    const repoConnector = screen.getByTestId("repo-connector");
    const launchButton =
      await within(repoConnector).findByTestId("repo-launch-button");
    await userEvent.click(launchButton);

    // repo not selected yet
    expect(createConversationSpy).not.toHaveBeenCalled();

    // Mock the repository branches API call
    vi.spyOn(GitService, "getRepositoryBranches").mockResolvedValue({
      items: [
        { name: "main", commit_sha: "123", protected: false },
        { name: "develop", commit_sha: "456", protected: false },
      ],
      next_page_id: null,
    });

    // First select the provider
    const providerDropdown = await waitFor(() =>
      screen.getByTestId("git-provider-dropdown"),
    );
    await userEvent.click(providerDropdown);
    await userEvent.click(screen.getByText("GitHub"));

    // Then select the repository
    const repoInput = await waitFor(() =>
      within(repoConnector).getByTestId("git-repo-dropdown"),
    );

    await userEvent.click(repoInput);

    // Wait for the options to be loaded and displayed
    await waitFor(() => {
      expect(screen.getByText("rbren/polaris")).toBeInTheDocument();
    });
    await userEvent.click(screen.getByText("rbren/polaris"));

    // Wait for the branch to be auto-selected
    await waitFor(() => {
      const branchInput = screen.getByTestId("git-branch-dropdown-input");
      expect(branchInput).toHaveValue("main");
    });

    await userEvent.click(launchButton);

    expect(createConversationSpy).toHaveBeenCalledOnce();
    expect(createConversationSpy).toHaveBeenCalledWith(
      "rbren/polaris",
      "github",
      undefined,
      "main",
      undefined,
      undefined,
      undefined,
      undefined,
      undefined,
      undefined,
    );
  });

  it("should change the launch button text to 'Loading...' when creating a conversation", async () => {
    const createConversationSpy = vi.spyOn(
      V1ConversationService,
      "createConversation",
    );
    createConversationSpy.mockImplementation(() => new Promise(() => { })); // Never resolves to keep loading state
    const retrieveUserGitRepositoriesSpy = vi.spyOn(
      GitService,
      "retrieveUserGitRepositories",
    );
    retrieveUserGitRepositoriesSpy.mockResolvedValue({
      items: MOCK_RESPOSITORIES,
      next_page_id: null,
    });

    // Mock the repository branches API call
    vi.spyOn(GitService, "getRepositoryBranches").mockResolvedValue({
      items: [
        { name: "main", commit_sha: "123", protected: false },
        { name: "develop", commit_sha: "456", protected: false },
      ],
      next_page_id: null,
    });

    renderRepoConnector();

    const launchButton = await screen.findByTestId("repo-launch-button");

    // First select the provider
    const providerDropdown = await waitFor(() =>
      screen.getByTestId("git-provider-dropdown"),
    );
    await userEvent.click(providerDropdown);
    await userEvent.click(screen.getByText("GitHub"));

    // Then select the repository
    const repoInput = await waitFor(() =>
      screen.getByTestId("git-repo-dropdown"),
    );

    await userEvent.click(repoInput);

    // Wait for the options to be loaded and displayed
    await waitFor(() => {
      expect(screen.getByText("rbren/polaris")).toBeInTheDocument();
    });
    await userEvent.click(screen.getByText("rbren/polaris"));

    // Wait for the branch to be auto-selected
    await waitFor(() => {
      const branchInput = screen.getByTestId("git-branch-dropdown-input");
      expect(branchInput).toHaveValue("main");
    });

    await userEvent.click(launchButton);
    expect(launchButton).toBeDisabled();
    expect(launchButton).toHaveTextContent(/Loading/i);
  });

  it("should not display a button to settings if the user is signed in with their git provider", async () => {
    renderRepoConnector();

    await waitFor(() => {
      expect(
        screen.queryByTestId("navigate-to-settings-button"),
      ).not.toBeInTheDocument();
    });
  });

  it("should display a button to settings if the user needs to sign in with their git provider", async () => {
    const getSettingsSpy = vi.spyOn(SettingsService, "getSettings");
    getSettingsSpy.mockResolvedValue({
      ...MOCK_DEFAULT_USER_SETTINGS,
      provider_tokens_set: {},
    });
    renderRepoConnector();

    const goToSettingsButton = await screen.findByTestId(
      "navigate-to-settings-button",
    );
    const dropdown = screen.queryByTestId("git-repo-dropdown");
    const launchButton = screen.queryByTestId("repo-launch-button");
    const providerLinks = screen.queryAllByText(/add git(hub|lab) repos/i);

    expect(dropdown).not.toBeInTheDocument();
    expect(launchButton).not.toBeInTheDocument();
    expect(providerLinks.length).toBe(0);

    expect(goToSettingsButton).toBeInTheDocument();

    await userEvent.click(goToSettingsButton);
    await screen.findByTestId("git-settings-screen");
  });
});
