import * as api from "@client/api";
import { useProfile } from "@client/hooks/useProfile";
import { _resetTracerReadinessCache } from "@client/hooks/useTracerReadiness";
import { renderWithQueryClient } from "@client/test/renderWithQueryClient";
import { createJob as createBaseJob } from "@shared/testing/factories.js";
import type { Job } from "@shared/types.js";
import { fireEvent, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { TailorMode } from "./TailorMode";

const render = (ui: Parameters<typeof renderWithQueryClient>[0]) =>
  renderWithQueryClient(ui);

vi.mock("@client/api", () => ({
  getResumeProjectsCatalog: vi.fn().mockResolvedValue([]),
  updateJob: vi.fn(),
  summarizeJob: vi.fn(),
  getTracerReadiness: vi.fn(),
}));

vi.mock("@client/hooks/useProfile", () => ({
  useProfile: vi.fn(),
}));

vi.mock("sonner", () => ({
  toast: {
    success: vi.fn(),
    error: vi.fn(),
  },
}));

const createJob = (overrides: Partial<Job> = {}): Job =>
  createBaseJob({
    id: "job-1",
    tailoredSummary: "Saved summary",
    tailoredHeadline: "Saved headline",
    tailoredSkills: JSON.stringify([
      { name: "Core", keywords: ["React", "TypeScript"] },
    ]),
    jobDescription: "Saved description",
    selectedProjectIds: "p1",
    ...overrides,
  });

const ensureAccordionOpen = (name: string) => {
  const trigger = screen.getByRole("button", { name });
  if (trigger.getAttribute("aria-expanded") !== "true") {
    fireEvent.click(trigger);
  }
};

describe("TailorMode", () => {
  beforeEach(() => {
    vi.clearAllMocks();
    _resetTracerReadinessCache();
    vi.mocked(api.getTracerReadiness).mockResolvedValue({
      status: "ready",
      canEnable: true,
      publicBaseUrl: "https://my-jobops.example.com",
      healthUrl: "https://my-jobops.example.com/health",
      checkedAt: Date.now(),
      lastSuccessAt: Date.now(),
      reason: null,
    });
    vi.mocked(useProfile).mockReturnValue({
      profile: {
        basics: {
          summary: "Original base summary",
          label: "Original base headline",
        },
        sections: {
          skills: {
            items: [
              {
                id: "s1",
                name: "Backend",
                description: "",
                level: 0,
                keywords: ["Node.js", "TypeScript"],
                visible: true,
              },
            ],
          },
        },
      },
      error: null,
      isLoading: false,
      personName: "Resume",
      refreshProfile: vi.fn(),
    });
  });

  it("does not rehydrate local edits from same-job prop updates", async () => {
    const { rerender } = render(
      <TailorMode
        job={createJob()}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );
    await waitFor(() =>
      expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
    );
    ensureAccordionOpen("Summary");

    fireEvent.change(screen.getByLabelText("Tailored Summary"), {
      target: { value: "Local draft" },
    });

    rerender(
      <TailorMode
        job={createJob({ tailoredSummary: "Older server value" })}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );
    ensureAccordionOpen("Summary");

    expect(screen.getByLabelText("Tailored Summary")).toHaveValue(
      "Local draft",
    );
  });

  it("allows finalize when summary exists even if no project is selected", async () => {
    render(
      <TailorMode
        job={createJob({ selectedProjectIds: "" })}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );

    expect(
      await screen.findByRole("button", { name: "Finalize & Move to Ready" }),
    ).toBeEnabled();
  });

  it("hides selected projects section when catalog is empty after load", async () => {
    render(
      <TailorMode
        job={createJob()}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );

    await waitFor(() =>
      expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
    );
    await waitFor(() =>
      expect(
        screen.queryByRole("button", { name: "Selected Projects" }),
      ).not.toBeInTheDocument(),
    );
  });

  it("resets local state when job id changes", async () => {
    const { rerender } = render(
      <TailorMode
        job={createJob()}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );
    await waitFor(() =>
      expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
    );
    ensureAccordionOpen("Summary");

    fireEvent.change(screen.getByLabelText("Tailored Summary"), {
      target: { value: "Local draft" },
    });

    rerender(
      <TailorMode
        job={createJob({
          id: "job-2",
          tailoredSummary: "New job summary",
          tailoredHeadline: "New job headline",
          tailoredSkills: JSON.stringify([
            { name: "Backend", keywords: ["Node.js", "Postgres"] },
          ]),
          jobDescription: "New job description",
          selectedProjectIds: "",
        })}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );
    ensureAccordionOpen("Summary");
    ensureAccordionOpen("Headline");
    ensureAccordionOpen("Tailored Skills");
    ensureAccordionOpen("Backend");

    expect(screen.getByLabelText("Tailored Summary")).toHaveValue(
      "New job summary",
    );
    expect(screen.getByLabelText("Tailored Headline")).toHaveValue(
      "New job headline",
    );
    expect(screen.getByDisplayValue("Node.js, Postgres")).toBeInTheDocument();
  });

  it("does not sync same-job props while summary field is focused", async () => {
    const { rerender } = render(
      <TailorMode
        job={createJob()}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );
    await waitFor(() =>
      expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
    );
    ensureAccordionOpen("Summary");

    const summary = screen.getByLabelText("Tailored Summary");
    fireEvent.focus(summary);

    rerender(
      <TailorMode
        job={createJob({ tailoredSummary: "Incoming from poll" })}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );
    ensureAccordionOpen("Summary");

    expect(screen.getByLabelText("Tailored Summary")).toHaveValue(
      "Saved summary",
    );
  });

  it("does not clobber local headline edits from same-job prop updates", async () => {
    const { rerender } = render(
      <TailorMode
        job={createJob()}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );
    await waitFor(() =>
      expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
    );
    ensureAccordionOpen("Headline");

    fireEvent.change(screen.getByLabelText("Tailored Headline"), {
      target: { value: "Local headline draft" },
    });

    rerender(
      <TailorMode
        job={createJob({ tailoredHeadline: "Incoming headline from poll" })}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );
    ensureAccordionOpen("Headline");

    expect(screen.getByLabelText("Tailored Headline")).toHaveValue(
      "Local headline draft",
    );
  });

  it("hydrates headline and skills after AI draft generation", async () => {
    vi.mocked(api.summarizeJob).mockResolvedValueOnce({
      ...createJob(),
      tailoredSummary: "AI summary",
      tailoredHeadline: "AI headline",
      tailoredSkills: JSON.stringify([
        { name: "Backend", keywords: ["Node.js", "Kafka"] },
      ]),
    } as Job);

    render(
      <TailorMode
        job={createJob()}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );
    await waitFor(() =>
      expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
    );

    fireEvent.click(screen.getByRole("button", { name: "Generate draft" }));

    await waitFor(() => ensureAccordionOpen("Headline"));
    expect(screen.getByLabelText("Tailored Headline")).toHaveValue(
      "AI headline",
    );
    ensureAccordionOpen("Tailored Skills");
    ensureAccordionOpen("Backend");
    expect(screen.getByDisplayValue("Backend")).toBeInTheDocument();
    expect(screen.getByDisplayValue("Node.js, Kafka")).toBeInTheDocument();
  });

  it("supports undo to template and redo to AI draft", async () => {
    render(
      <TailorMode
        job={createJob()}
        onBack={vi.fn()}
        onFinalize={vi.fn()}
        isFinalizing={false}
      />,
    );
    await waitFor(() =>
      expect(api.getResumeProjectsCatalog).toHaveBeenCalled(),
    );

    ensureAccordionOpen("Summary");
    fireEvent.click(screen.getAllByLabelText("Undo to template")[0]);
    expect(screen.getByLabelText("Tailored Summary")).toHaveValue(
      "Original base summary",
    );
    fireEvent.click(screen.getAllByLabelText("Redo to AI draft")[0]);
    expect(screen.getByLabelText("Tailored Summary")).toHaveValue(
      "Saved summary",
    );

    ensureAccordionOpen("Headline");
    fireEvent.click(screen.getAllByLabelText("Undo to template")[1]);
    expect(screen.getByLabelText("Tailored Headline")).toHaveValue(
      "Original base headline",
    );
    fireEvent.click(screen.getAllByLabelText("Redo to AI draft")[1]);
    expect(screen.getByLabelText("Tailored Headline")).toHaveValue(
      "Saved headline",
    );

    ensureAccordionOpen("Tailored Skills");
    fireEvent.click(screen.getAllByLabelText("Undo to template")[2]);
    ensureAccordionOpen("Backend");
    expect(screen.getByDisplayValue("Node.js, TypeScript")).toBeInTheDocument();
    fireEvent.click(screen.getAllByLabelText("Redo to AI draft")[2]);
    ensureAccordionOpen("Core");
    expect(screen.getByDisplayValue("React, TypeScript")).toBeInTheDocument();
  });
});
