import CalendarPopover from "@/components/Calendar/CalendarModal";
import { getSecretLink } from "@/features/Calendars/CalendarApi";
import { Calendar } from "@/features/Calendars/CalendarTypes";
import * as eventThunks from "@/features/Calendars/services";
import * as delegationThunks from "@/features/Calendars/services/updateDelegationCalendarAsync";
import { fireEvent, screen, waitFor } from "@testing-library/react";
import { renderWithProviders } from "../../utils/Renderwithproviders";

jest.mock("@/features/Calendars/CalendarApi", () => ({
  getSecretLink: jest.fn(),
}));

const mockThunkWithUnwrap = (resolvedValue: unknown = {}) =>
  jest.fn().mockImplementation(() => {
    const dispatchResult = Object.assign(Promise.resolve(resolvedValue), {
      unwrap: () => Promise.resolve(resolvedValue),
    });
    return jest.fn().mockReturnValue(dispatchResult);
  });

describe("CalendarPopover", () => {
  const mockOnClose = jest.fn();

  beforeEach(() => {
    jest.clearAllMocks();
    jest
      .spyOn(delegationThunks, "updateDelegationCalendarAsync")
      .mockImplementation(mockThunkWithUnwrap());
  });

  const renderPopover = (open = true) => {
    const preloadedState = {
      user: {
        userData: {
          sub: "test",
          email: "test@test.com",
          sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
          openpaasId: "667037022b752d0026472254",
        },
      },
      calendars: { list: {}, pending: true },
    };
    renderWithProviders(
      <CalendarPopover open={open} onClose={mockOnClose} />,
      preloadedState
    );
  };

  it("renders popover and inputs", () => {
    renderPopover();

    expect(screen.getByLabelText(/Name/i)).toBeInTheDocument();
    expect(screen.getByText("event.form.addDescription")).toBeInTheDocument();
  });

  it("updates name and description fields", () => {
    renderPopover();

    const nameInput = screen.getByLabelText(/Name/i);
    fireEvent.change(nameInput, { target: { value: "My Calendar" } });
    expect(nameInput).toHaveValue("My Calendar");

    fireEvent.click(screen.getByText("event.form.addDescription"));
    const descInput = screen.getByLabelText(/Description/i);
    fireEvent.change(descInput, { target: { value: "Test description" } });
    expect(descInput).toHaveValue("Test description");
  });

  it("dispatches createCalendar and calls onClose when Save clicked", async () => {
    jest
      .spyOn(eventThunks, "createCalendarAsync")
      .mockImplementation(mockThunkWithUnwrap());

    renderPopover();

    fireEvent.change(screen.getByLabelText(/Name/i), {
      target: { value: "Test Calendar" },
    });
    fireEvent.click(screen.getByText("event.form.addDescription"));
    fireEvent.change(screen.getByLabelText(/Description/i), {
      target: { value: "Test Description" },
    });

    const colorButtons = screen.getAllByRole("button", {
      name: /select color/i,
    });
    fireEvent.click(colorButtons[0]);

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

    await waitFor(() =>
      expect(eventThunks.createCalendarAsync).toHaveBeenCalled()
    );
    await waitFor(() =>
      expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick")
    );
  });

  it("calls onClose when Cancel clicked", () => {
    renderPopover();

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

    expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
  });
});

describe("CalendarPopover (editing mode)", () => {
  const mockOnClose = jest.fn();

  const baseUser = {
    userData: {
      sub: "test",
      email: "test@test.com",
      sid: "mockSid",
      openpaasId: "user1",
    },
  };

  const existingCalendar: Calendar = {
    id: "user1/cal1",
    link: "/calendars/user/cal1",
    name: "Work Calendar",
    description: "Team meetings",
    color: { light: "#33B679" },
    owner: { firstname: "alice", emails: ["alice@example.com"] },
    visibility: "public",
    events: {},
  };

  beforeEach(() => {
    jest.clearAllMocks();
    jest
      .spyOn(delegationThunks, "updateDelegationCalendarAsync")
      .mockImplementation(mockThunkWithUnwrap());
  });

  it("prefills fields when calendar prop is given", () => {
    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    );

    expect(screen.getByLabelText(/Name/i)).toHaveValue("Work Calendar");
    expect(screen.getByLabelText(/Description/i)).toHaveValue("Team meetings");
  });

  test("Save button is disabled when name is empty or whitespace only", () => {
    renderWithProviders(<CalendarPopover open={true} onClose={jest.fn()} />, {
      user: baseUser,
    });

    const saveButton = screen.getByRole("button", { name: /create/i });
    expect(saveButton).toBeDisabled();
    // only spaces
    const nameInput = screen.getByLabelText(/name/i);
    fireEvent.change(nameInput, { target: { value: "    " } });

    expect(saveButton).toBeDisabled();

    // valid name
    fireEvent.change(nameInput, { target: { value: "Work Calendar" } });
    expect(saveButton).toBeEnabled();
  });

  it("allows modifying and saving existing calendar", async () => {
    jest
      .spyOn(eventThunks, "patchCalendarAsync")
      .mockImplementation(mockThunkWithUnwrap());

    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    );

    // Change name
    fireEvent.change(screen.getByLabelText(/Name/i), {
      target: { value: "Updated Calendar" },
    });

    // Save
    fireEvent.click(screen.getByRole("button", { name: "actions.save" }));

    await waitFor(() =>
      expect(eventThunks.patchCalendarAsync).toHaveBeenCalledWith(
        expect.objectContaining({
          calId: "user1/cal1",
          calLink: "/calendars/user/cal1",
          patch: {
            color: { light: "#33B679" },
            desc: "Team meetings",
            name: "Updated Calendar",
          },
        })
      )
    );
    await waitFor(() => expect(mockOnClose).toHaveBeenCalled());
  });
});

describe("CalendarPopover - Tabs Scenarios", () => {
  const mockOnClose = jest.fn();
  const baseUser = {
    userData: {
      openpaasId: "user1",
    },
  };

  const writeText = jest.fn();

  Object.assign(navigator, {
    clipboard: {
      writeText,
    },
  });

  const existingCalendar: Calendar = {
    id: "user1/cal1",
    link: "/calendars/user1/cal1.json",
    name: "Work Calendar",
    description: "Team meetings",
    color: { light: "#33B679" },
    owner: { firstname: "alice", emails: ["alice@example.com"] },
    visibility: "public",
    events: {},
  };

  beforeEach(() => {
    jest.clearAllMocks();
    jest
      .spyOn(delegationThunks, "updateDelegationCalendarAsync")
      .mockImplementation(mockThunkWithUnwrap());
  });

  it("resets state after closing and reopening", () => {
    const { rerender } = renderWithProviders(
      <CalendarPopover open={true} onClose={mockOnClose} />,
      { user: baseUser }
    );

    // Enter some data
    fireEvent.change(screen.getByLabelText(/Name/i), {
      target: { value: "Temp Calendar" },
    });
    fireEvent.click(screen.getByRole("button", { name: /Cancel/i }));

    expect(mockOnClose).toHaveBeenCalled();

    // Reopen: state should be reset
    rerender(<CalendarPopover open={true} onClose={mockOnClose} />);
    expect(screen.getByLabelText(/Name/i)).toHaveValue("");
  });

  it("shows Access tab only when editing an existing calendar", () => {
    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    );

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

  it("does not show Access tab when creating new calendar", () => {
    renderWithProviders(<CalendarPopover open={true} onClose={mockOnClose} />, {
      user: baseUser,
    });

    expect(
      screen.queryByRole("tab", { name: /Access/i })
    ).not.toBeInTheDocument();
  });

  it("patches ACL when visibility changes", async () => {
    jest
      .spyOn(eventThunks, "patchCalendarAsync")
      .mockImplementation(mockThunkWithUnwrap());

    jest
      .spyOn(eventThunks, "patchACLCalendarAsync")
      .mockImplementation(mockThunkWithUnwrap());

    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    );

    // By default: "All" (public) is selected
    const publicButton = screen.getByRole("button", { name: /All/i });
    const privateButton = screen.getByRole("button", { name: /You/i });

    expect(publicButton).toHaveAttribute("aria-pressed", "true");
    expect(privateButton).toHaveAttribute("aria-pressed", "false");

    // Change to private
    fireEvent.click(privateButton);

    expect(privateButton).toHaveAttribute("aria-pressed", "true");
    expect(publicButton).toHaveAttribute("aria-pressed", "false");

    // Save
    fireEvent.click(screen.getByRole("button", { name: "actions.save" }));

    await waitFor(() =>
      expect(eventThunks.patchACLCalendarAsync).toHaveBeenCalledWith(
        expect.objectContaining({
          calId: "user1/cal1",
          request: "",
        })
      )
    );
  });

  it("copies CalDAV link from Access tab", async () => {
    window.DAV_BASE_URL = "https://cal.example.org";
    Object.assign(navigator, {
      clipboard: { writeText: jest.fn() },
    });
    (getSecretLink as jest.Mock).mockResolvedValue({
      secretLink: "https://example.org/secret/initial",
    });

    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    );

    // Switch to Access tab
    fireEvent.click(screen.getByRole("tab", { name: /Access/i }));

    // Expect text field with caldav link
    const input = screen.getByLabelText("calendar.caldav_access");
    expect(input).toHaveValue("https://cal.example.org/calendars/user1/cal1");

    // Click copy button (find button containing ContentCopyIcon)
    const copyIcon = screen.getAllByTestId("ContentCopyIcon")[0];
    const copyButton = copyIcon.closest("button");
    if (copyButton) {
      fireEvent.click(copyButton);
    }

    expect(navigator.clipboard.writeText).toHaveBeenCalledWith(
      "https://cal.example.org/calendars/user1/cal1"
    );

    // Snackbar should appear
    await waitFor(() =>
      expect(screen.getByText("common.link_copied")).toBeInTheDocument()
    );
  });

  describe("Import flow", () => {
    const file = new File(["test"], "events.ics", { type: "text/calendar" });

    it("creates a new calendar and imports events when Import with 'new' target", async () => {
      jest
        .spyOn(eventThunks, "createCalendarAsync")
        .mockImplementation(mockThunkWithUnwrap());
      jest
        .spyOn(eventThunks, "importEventFromFileAsync")
        .mockImplementation(mockThunkWithUnwrap());

      renderWithProviders(
        <CalendarPopover open={true} onClose={mockOnClose} />,
        {
          user: baseUser,
        }
      );

      // Switch to Import tab
      fireEvent.click(screen.getByRole("tab", { name: /Import/i }));

      // Provide new calendar params
      fireEvent.change(screen.getByLabelText(/Name/i), {
        target: { value: "Imported Calendar" },
      });
      const fileInput = screen.getByLabelText("common.select_file");
      fireEvent.change(fileInput, { target: { files: [file] } });

      // Click Import
      fireEvent.click(screen.getByRole("button", { name: "actions.import" }));

      await waitFor(() =>
        expect(eventThunks.createCalendarAsync).toHaveBeenCalled()
      );
      await waitFor(() =>
        expect(eventThunks.importEventFromFileAsync).toHaveBeenCalled()
      );
    });

    it("imports into an existing calendar when target is set", async () => {
      jest
        .spyOn(eventThunks, "importEventFromFileAsync")
        .mockImplementation(mockThunkWithUnwrap());

      const calendars = {
        "user1/cal1": existingCalendar,
      };

      renderWithProviders(
        <CalendarPopover
          open={true}
          onClose={mockOnClose}
          calendar={existingCalendar}
        />,
        { user: baseUser, calendars: { list: calendars } }
      );

      fireEvent.click(screen.getByRole("tab", { name: /Import/i }));
      const fileInput = screen.getByLabelText("common.select_file");
      fireEvent.change(fileInput, { target: { files: [file] } });

      fireEvent.click(screen.getByRole("button", { name: "actions.import" }));

      await waitFor(() =>
        expect(eventThunks.importEventFromFileAsync).toHaveBeenCalledWith(
          expect.objectContaining({
            calLink: "/calendars/user1/cal1.json",
            file,
          })
        )
      );
    });

    it("disables Import button until a file is uploaded", () => {
      renderWithProviders(
        <CalendarPopover open={true} onClose={mockOnClose} />,
        {
          user: baseUser,
        }
      );

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

      const importButton = screen.getByRole("button", {
        name: "actions.import",
      });
      expect(importButton).toBeDisabled();
    });
  });

  it("fetches and resets the secret link", async () => {
    window.DAV_BASE_URL = "https://cal.example.org";

    (getSecretLink as jest.Mock)
      .mockResolvedValueOnce({
        secretLink: "https://example.org/secret/initial",
      })
      .mockResolvedValueOnce({
        secretLink: "https://example.org/secret/new",
      });

    renderWithProviders(
      <CalendarPopover
        open={true}
        onClose={mockOnClose}
        calendar={existingCalendar}
      />,
      { user: baseUser }
    );

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

    await waitFor(() =>
      expect(
        screen.getByDisplayValue("https://example.org/secret/initial")
      ).toBeInTheDocument()
    );

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

    await waitFor(() =>
      expect(
        screen.getByDisplayValue("https://example.org/secret/new")
      ).toBeInTheDocument()
    );

    expect(getSecretLink).toHaveBeenCalledWith(
      existingCalendar.link.replace(".json", ""),
      true
    );
  });
});
