import { cleanup, fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";

import { ToolWorkshopPage } from "./tool-workshop";
import * as toolTypesApi from "../api/tool_types";
import * as toolConfigsApi from "../api/tool_configs";
import * as configFoldersApi from "../api/config_folders";

const mockToolTypes = [
  {
    id: "type-1",
    name: "code-server",
    display_name: "VS Code Server",
    description: "VS Code in browser",
    category: "editor",
    interface_type: "web",
    requires_port: true,
    default_port: 8443,
    definition_type: "compose",
    compose_template: "version: '3.8'\\nservices:\\n  app:\\n    image: codercom/code-server",
    dockerfile_template: null,
    build_context: null,
    readiness_probe: null,
    required_variables: ["REPO_PATH"],
    is_builtin: true,
    created_by_id: null,
    created_at: "2024-01-01T00:00:00Z",
    updated_at: "2024-01-01T00:00:00Z",
  },
  {
    id: "type-2",
    name: "custom-tool",
    display_name: "Custom Tool",
    description: "My custom tool",
    category: "utility",
    interface_type: "terminal",
    requires_port: false,
    default_port: 8080,
    definition_type: "dockerfile",
    compose_template: null,
    dockerfile_template: "FROM python:3.11",
    build_context: null,
    readiness_probe: {
      command: "python --version",
      timeout: 30,
      interval: 2,
    },
    required_variables: [],
    is_builtin: false,
    created_by_id: "user-1",
    created_at: "2024-01-01T00:00:00Z",
    updated_at: "2024-01-01T00:00:00Z",
  },
];

const mockConfigs = [
  {
    id: "config-1",
    tool_type_id: "type-1",
    project_id: null,
    key: "OPENAI_API_KEY",
    value: "sk-test123",
    config_type: "env",
    file_path: null,
    port_override: null,
    start_command: null,
    working_directory: null,
    environment_variables: {},
    volumes: [],
  },
  {
    id: "config-2",
    tool_type_id: "type-2",
    project_id: null,
    key: "advanced-config",
    value: "test-value",
    config_type: "env",
    file_path: null,
    port_override: 9090,
    start_command: "python app.py",
    working_directory: "/app",
    environment_variables: { DEBUG: "true" },
    volumes: [{ source: "data", target: "/data", type: "bind" }],
  },
];

const mockFolders = [
  {
    id: "folder-1",
    user_id: "user-1",
    name: "my-dotfiles",
    description: "My personal config files",
    mount_path: "/home/user",
    files: { ".zshrc": "export ZSH=\"$HOME/.oh-my-zsh\"" },
    project_overrides: {},
    is_active: true,
    created_at: "2024-01-01T00:00:00Z",
    updated_at: "2024-01-01T00:00:00Z",
  },
  {
    id: "folder-2",
    user_id: "user-1",
    name: "project-configs",
    description: "Project specific configs",
    mount_path: "/workspace",
    files: { ".env": "API_URL=http://localhost:8080" },
    project_overrides: {
      "proj-1": {
        mount_path: "/app",
        files: { ".env": "API_URL=http://prod.api" },
      },
    },
    is_active: false,
    created_at: "2024-01-01T00:00:00Z",
    updated_at: "2024-01-01T00:00:00Z",
  },
];

afterEach(() => {
  cleanup();
  vi.restoreAllMocks();
});

describe("ToolWorkshopPage", () => {
  it("renders loading state initially", () => {
    vi.spyOn(toolTypesApi, "listToolTypes").mockImplementation(() => new Promise(() => {}));
    vi.spyOn(toolConfigsApi, "listToolConfigs").mockImplementation(() => new Promise(() => {}));
    vi.spyOn(configFoldersApi, "listConfigFolders").mockImplementation(() => new Promise(() => {}));

    render(<ToolWorkshopPage />);
    expect(screen.getByText(/loading/i)).toBeInTheDocument();
  });

  it("renders tool types tab by default", async () => {
    vi.spyOn(toolTypesApi, "listToolTypes").mockResolvedValue(mockToolTypes as unknown as toolTypesApi.ToolType[]);
    vi.spyOn(toolConfigsApi, "listToolConfigs").mockResolvedValue(mockConfigs as unknown as toolConfigsApi.ToolConfig[]);
    vi.spyOn(configFoldersApi, "listConfigFolders").mockResolvedValue(mockFolders as unknown as configFoldersApi.ConfigFolder[]);

    render(<ToolWorkshopPage />);

    await waitFor(() => {
      expect(screen.getByText("VS Code Server")).toBeInTheDocument();
    });
    expect(screen.getByText("Custom Tool")).toBeInTheDocument();
  });

  it("switches to configs tab", async () => {
    vi.spyOn(toolTypesApi, "listToolTypes").mockResolvedValue(mockToolTypes as unknown as toolTypesApi.ToolType[]);
    vi.spyOn(toolConfigsApi, "listToolConfigs").mockResolvedValue(mockConfigs as unknown as toolConfigsApi.ToolConfig[]);
    vi.spyOn(configFoldersApi, "listConfigFolders").mockResolvedValue(mockFolders as unknown as configFoldersApi.ConfigFolder[]);

    render(<ToolWorkshopPage />);

    await waitFor(() => {
      expect(screen.getByText("VS Code Server")).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText("VS Code Server"));

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /configs/i })).toBeInTheDocument();
    });

    fireEvent.click(screen.getByRole("button", { name: /configs/i }));

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

    fireEvent.click(screen.getByRole("button", { name: /add config/i }));

    expect(screen.getByPlaceholderText("e.g., OPENAI_API_KEY")).toBeInTheDocument();
    expect(screen.getByPlaceholderText(/Enter value/i)).toBeInTheDocument();
  });

  it("creates config with advanced fields", async () => {
    vi.spyOn(toolTypesApi, "listToolTypes").mockResolvedValue(mockToolTypes as unknown as toolTypesApi.ToolType[]);
    const configsListMock = vi.spyOn(toolConfigsApi, "listToolConfigs").mockResolvedValue(mockConfigs as unknown as toolConfigsApi.ToolConfig[]);
    const createMock = vi.spyOn(toolConfigsApi, "createToolConfig").mockResolvedValue(mockConfigs[1] as unknown as toolConfigsApi.ToolConfig);
    vi.spyOn(configFoldersApi, "listConfigFolders").mockResolvedValue(mockFolders as unknown as configFoldersApi.ConfigFolder[]);

    render(<ToolWorkshopPage />);

    await waitFor(() => {
      expect(screen.getByText("VS Code Server")).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText("VS Code Server"));

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /configs/i })).toBeInTheDocument();
    });

    fireEvent.click(screen.getByRole("button", { name: /configs/i }));

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

    fireEvent.click(screen.getByRole("button", { name: /add config/i }));

    fireEvent.change(screen.getByPlaceholderText("e.g., OPENAI_API_KEY"), {
      target: { value: "MY_CONFIG" },
    });
    fireEvent.change(screen.getByPlaceholderText(/Enter value/i), {
      target: { value: "my-value" },
    });
    fireEvent.change(screen.getByPlaceholderText("e.g., 8080"), {
      target: { value: "9090" },
    });
    fireEvent.change(screen.getByPlaceholderText("e.g., npm start"), {
      target: { value: "python app.py" },
    });

    fireEvent.click(screen.getByRole("button", { name: /add$/i }));

    await waitFor(() => {
      expect(createMock).toHaveBeenCalledWith(
        expect.objectContaining({
          key: "MY_CONFIG",
          value: "my-value",
          port_override: 9090,
          start_command: "python app.py",
        })
      );
    });
    expect(configsListMock).toHaveBeenCalledTimes(2);
  });

  it("opens folder creation form", async () => {
    vi.spyOn(toolTypesApi, "listToolTypes").mockResolvedValue(mockToolTypes as unknown as toolTypesApi.ToolType[]);
    vi.spyOn(toolConfigsApi, "listToolConfigs").mockResolvedValue(mockConfigs as unknown as toolConfigsApi.ToolConfig[]);
    vi.spyOn(configFoldersApi, "listConfigFolders").mockResolvedValue(mockFolders as unknown as configFoldersApi.ConfigFolder[]);

    render(<ToolWorkshopPage />);

    await waitFor(() => {
      expect(screen.getByText("VS Code Server")).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText("VS Code Server"));

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /folders/i })).toBeInTheDocument();
    });

    fireEvent.click(screen.getByRole("button", { name: /folders/i }));

    await waitFor(() => {
      expect(screen.getByText("my-dotfiles")).toBeInTheDocument();
    });

    fireEvent.click(screen.getByRole("button", { name: /create folder/i }));

    expect(screen.getByPlaceholderText("e.g., my-dotfiles")).toBeInTheDocument();
    expect(screen.getByPlaceholderText("e.g., /home/user")).toBeInTheDocument();
  });

  it("creates config folder successfully", async () => {
    const foldersListMock = vi.spyOn(configFoldersApi, "listConfigFolders").mockResolvedValue(mockFolders as unknown as configFoldersApi.ConfigFolder[]);
    const createMock = vi.spyOn(configFoldersApi, "createConfigFolder").mockResolvedValue(mockFolders[0] as unknown as configFoldersApi.ConfigFolder);
    vi.spyOn(toolTypesApi, "listToolTypes").mockResolvedValue(mockToolTypes as unknown as toolTypesApi.ToolType[]);
    vi.spyOn(toolConfigsApi, "listToolConfigs").mockResolvedValue(mockConfigs as unknown as toolConfigsApi.ToolConfig[]);

    render(<ToolWorkshopPage />);

    await waitFor(() => {
      expect(screen.getByText("VS Code Server")).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText("VS Code Server"));

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /folders/i })).toBeInTheDocument();
    });

    fireEvent.click(screen.getByRole("button", { name: /folders/i }));

    await waitFor(() => {
      expect(screen.getByText("my-dotfiles")).toBeInTheDocument();
    });

    fireEvent.click(screen.getByRole("button", { name: /create folder/i }));

    fireEvent.change(screen.getByPlaceholderText("e.g., my-dotfiles"), {
      target: { value: "new-folder" },
    });
    fireEvent.change(screen.getByPlaceholderText("e.g., /home/user"), {
      target: { value: "/home/dev" },
    });

    fireEvent.click(screen.getByRole("button", { name: /create$/i }));

    await waitFor(() => {
      expect(createMock).toHaveBeenCalledWith(
        expect.objectContaining({
          name: "new-folder",
          mount_path: "/home/dev",
        })
      );
    });
    expect(foldersListMock).toHaveBeenCalledTimes(2);
  });

  it("shows folder active/inactive status", async () => {
    vi.spyOn(toolTypesApi, "listToolTypes").mockResolvedValue(mockToolTypes as unknown as toolTypesApi.ToolType[]);
    vi.spyOn(toolConfigsApi, "listToolConfigs").mockResolvedValue(mockConfigs as unknown as toolConfigsApi.ToolConfig[]);
    vi.spyOn(configFoldersApi, "listConfigFolders").mockResolvedValue(mockFolders as unknown as configFoldersApi.ConfigFolder[]);

    render(<ToolWorkshopPage />);

    await waitFor(() => {
      expect(screen.getByText("VS Code Server")).toBeInTheDocument();
    });

    fireEvent.click(screen.getByText("VS Code Server"));

    await waitFor(() => {
      expect(screen.getByRole("button", { name: /folders/i })).toBeInTheDocument();
    });

    fireEvent.click(screen.getByRole("button", { name: /folders/i }));

    await waitFor(() => {
      expect(screen.getByText("my-dotfiles")).toBeInTheDocument();
    });

    // Check that active folder shows Active badge
    expect(screen.getByText("Active")).toBeInTheDocument();
  });

  it("handles error state gracefully", async () => {
    vi.spyOn(toolTypesApi, "listToolTypes").mockRejectedValue(new Error("Network error"));
    vi.spyOn(toolConfigsApi, "listToolConfigs").mockRejectedValue(new Error("Network error"));
    vi.spyOn(configFoldersApi, "listConfigFolders").mockRejectedValue(new Error("Network error"));

    render(<ToolWorkshopPage />);

    await waitFor(() => {
      expect(screen.getByText(/failed to load/i)).toBeInTheDocument();
    });

    expect(screen.getByRole("button", { name: /retry/i })).toBeInTheDocument();
  });

  it("retries loading after error", async () => {
    const listMock = vi.spyOn(toolTypesApi, "listToolTypes")
      .mockRejectedValueOnce(new Error("Network error"))
      .mockResolvedValueOnce(mockToolTypes as unknown as toolTypesApi.ToolType[]);
    vi.spyOn(toolConfigsApi, "listToolConfigs")
      .mockRejectedValueOnce(new Error("Network error"))
      .mockResolvedValueOnce(mockConfigs as unknown as toolConfigsApi.ToolConfig[]);
    vi.spyOn(configFoldersApi, "listConfigFolders")
      .mockRejectedValueOnce(new Error("Network error"))
      .mockResolvedValueOnce(mockFolders as unknown as configFoldersApi.ConfigFolder[]);

    render(<ToolWorkshopPage />);

    await waitFor(() => {
      expect(screen.getByText(/failed to load/i)).toBeInTheDocument();
    });

    fireEvent.click(screen.getByRole("button", { name: /retry/i }));

    await waitFor(() => {
      expect(screen.getByText("VS Code Server")).toBeInTheDocument();
    });
    expect(listMock).toHaveBeenCalledTimes(2);
  });

  it("deletes tool type successfully", async () => {
    const listMock = vi.spyOn(toolTypesApi, "listToolTypes").mockResolvedValue(mockToolTypes as unknown as toolTypesApi.ToolType[]);
    const deleteMock = vi.spyOn(toolTypesApi, "deleteToolType").mockResolvedValue(undefined);
    vi.spyOn(toolConfigsApi, "listToolConfigs").mockResolvedValue(mockConfigs as unknown as toolConfigsApi.ToolConfig[]);
    vi.spyOn(configFoldersApi, "listConfigFolders").mockResolvedValue(mockFolders as unknown as configFoldersApi.ConfigFolder[]);

    render(<ToolWorkshopPage />);

    await waitFor(() => {
      expect(screen.getByText("Custom Tool")).toBeInTheDocument();
    });

    // Find and click delete button for custom tool (not built-in)
    const customToolCard = screen.getByText("Custom Tool").closest(".card") ||
                           screen.getByText("Custom Tool").parentElement;
    if (customToolCard) {
      const deleteButton = within(customToolCard as HTMLElement).queryByRole("button", { name: /delete/i });
      if (deleteButton) {
        vi.spyOn(window, "confirm").mockReturnValue(true);
        fireEvent.click(deleteButton);

        await waitFor(() => {
          expect(deleteMock).toHaveBeenCalledWith("type-2");
        });
        expect(listMock).toHaveBeenCalledTimes(2);
      }
    }
  });
});
