import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { render, screen } from "~/test-utils.js";
import userEvent from "@testing-library/user-event";
import { DateRangePresets } from "./DateRangePresets.js";

describe("DateRangePresets", () => {
  describe("rendering", () => {
    it("renders all four preset buttons", () => {
      render(<DateRangePresets onSelect={vi.fn()} />);
      expect(screen.getByRole("button", { name: "This month" })).toBeInTheDocument();
      expect(screen.getByRole("button", { name: "This quarter" })).toBeInTheDocument();
      expect(screen.getByRole("button", { name: "Next 3 months" })).toBeInTheDocument();
      expect(screen.getByRole("button", { name: "This year" })).toBeInTheDocument();
    });

    it("all buttons have type=button to avoid accidental form submission", () => {
      render(<DateRangePresets onSelect={vi.fn()} />);
      const buttons = screen.getAllByRole("button");
      for (const btn of buttons) {
        expect(btn).toHaveAttribute("type", "button");
      }
    });

    it("applies a custom className to the wrapper", () => {
      const { container } = render(
        <DateRangePresets onSelect={vi.fn()} className="my-custom-class" />,
      );
      expect(container.firstChild).toHaveClass("my-custom-class");
    });

    it("renders without error when className is omitted", () => {
      const { container } = render(<DateRangePresets onSelect={vi.fn()} />);
      expect(container.firstChild).toBeInTheDocument();
    });
  });

  describe("onSelect callback", () => {
    it("calls onSelect with start and end date strings when 'This month' is clicked", async () => {
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "This month" }));
      expect(onSelect).toHaveBeenCalledOnce();
      const [start, end] = onSelect.mock.calls[0] as [string, string];
      expect(start).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      expect(end).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      expect(start <= end).toBe(true);
    });

    it("calls onSelect with start and end date strings when 'This quarter' is clicked", async () => {
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "This quarter" }));
      expect(onSelect).toHaveBeenCalledOnce();
      const [start, end] = onSelect.mock.calls[0] as [string, string];
      expect(start).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      expect(end).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      expect(start <= end).toBe(true);
    });

    it("calls onSelect with start and end date strings when 'Next 3 months' is clicked", async () => {
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "Next 3 months" }));
      expect(onSelect).toHaveBeenCalledOnce();
      const [start, end] = onSelect.mock.calls[0] as [string, string];
      expect(start).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      expect(end).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      expect(start <= end).toBe(true);
    });

    it("calls onSelect with start and end date strings when 'This year' is clicked", async () => {
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "This year" }));
      expect(onSelect).toHaveBeenCalledOnce();
      const [start, end] = onSelect.mock.calls[0] as [string, string];
      expect(start).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      expect(end).toMatch(/^\d{4}-\d{2}-\d{2}$/);
      expect(start <= end).toBe(true);
    });

    it("calls onSelect exactly once per click and not before any interaction", async () => {
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      expect(onSelect).not.toHaveBeenCalled();
      await user.click(screen.getByRole("button", { name: "This year" }));
      expect(onSelect).toHaveBeenCalledTimes(1);
    });

    it("calls onSelect independently for each separate button click", async () => {
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "This month" }));
      await user.click(screen.getByRole("button", { name: "This year" }));
      expect(onSelect).toHaveBeenCalledTimes(2);
      const firstCall = onSelect.mock.calls[0] as [string, string];
      const secondCall = onSelect.mock.calls[1] as [string, string];
      // "This month" start should differ from "This year" end
      expect(firstCall[1]).not.toBe(secondCall[1]);
    });
  });

  describe("date correctness", () => {
    // The component uses `new Date(y, m, d).toISOString().slice(0, 10)` which
    // converts local-midnight dates to UTC. We compute expected values the same
    // way so the tests are timezone-safe.
    const toIso = (d: Date) => d.toISOString().slice(0, 10);

    beforeEach(() => {
      vi.useFakeTimers({ shouldAdvanceTime: true });
      vi.setSystemTime(new Date("2024-03-15T12:00:00"));
    });

    afterEach(() => {
      vi.useRealTimers();
    });

    it("'This month' returns the first and last day of the current month", async () => {
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "This month" }));
      const [start, end] = onSelect.mock.calls[0] as [string, string];
      expect(start).toBe(toIso(new Date(2024, 2, 1)));
      expect(end).toBe(toIso(new Date(2024, 3, 0)));
    });

    it("'This quarter' returns Q1 bounds for a March date", async () => {
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "This quarter" }));
      const [start, end] = onSelect.mock.calls[0] as [string, string];
      expect(start).toBe(toIso(new Date(2024, 0, 1)));
      expect(end).toBe(toIso(new Date(2024, 3, 0)));
    });

    it("'This year' returns Jan 1 to Dec 31 of the current year", async () => {
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "This year" }));
      const [start, end] = onSelect.mock.calls[0] as [string, string];
      expect(start).toBe(toIso(new Date(2024, 0, 1)));
      expect(end).toBe(toIso(new Date(2024, 11, 31)));
    });

    it("'Next 3 months' start equals today", async () => {
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "Next 3 months" }));
      const [start] = onSelect.mock.calls[0] as [string, string];
      expect(start).toBe(toIso(new Date(2024, 2, 15)));
    });

    it("'This quarter' returns Q2 bounds for an April date", async () => {
      vi.setSystemTime(new Date("2024-04-10T12:00:00"));
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "This quarter" }));
      const [start, end] = onSelect.mock.calls[0] as [string, string];
      expect(start).toBe(toIso(new Date(2024, 3, 1)));
      expect(end).toBe(toIso(new Date(2024, 6, 0)));
    });

    it("'This quarter' returns Q3 bounds for a July date", async () => {
      vi.setSystemTime(new Date("2024-07-01T12:00:00"));
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "This quarter" }));
      const [start, end] = onSelect.mock.calls[0] as [string, string];
      expect(start).toBe(toIso(new Date(2024, 6, 1)));
      expect(end).toBe(toIso(new Date(2024, 9, 0)));
    });

    it("'This quarter' returns Q4 bounds for an October date", async () => {
      vi.setSystemTime(new Date("2024-10-20T12:00:00"));
      const user = userEvent.setup();
      const onSelect = vi.fn();
      render(<DateRangePresets onSelect={onSelect} />);
      await user.click(screen.getByRole("button", { name: "This quarter" }));
      const [start, end] = onSelect.mock.calls[0] as [string, string];
      expect(start).toBe(toIso(new Date(2024, 9, 1)));
      expect(end).toBe(toIso(new Date(2024, 12, 0)));
    });
  });
});
