import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { render, screen, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { vi, beforeEach, afterEach } from "vitest";

import {
  PILOT_EMAIL_RE,
  PILOT_LIMITS,
  PilotRequestModal,
  formatSubmitError,
  validatePilotField,
  validatePilotForm,
} from "../PilotRequestModal";

// ── Schema parity with backend Pydantic PilotRequestInput ─────────────────
// `backend/app/api/v1/pilot.py` defines:
//   name: min_length=2, max_length=200
//   phone: max_length=50
//   email: max_length=200, pattern=r"^[^@\s]+@[^@\s]+\.[^@\s]+$"
//   company: max_length=200
//   message: max_length=2000
describe("PilotRequestModal — schema mirror", () => {
  it("PILOT_LIMITS matches backend constraints", () => {
    expect(PILOT_LIMITS).toEqual({
      name: { min: 2, max: 200 },
      phone: { max: 50 },
      email: { max: 200 },
      company: { max: 200 },
      message: { max: 2000 },
    });
  });

  it("PILOT_EMAIL_RE matches backend Pydantic pattern", () => {
    // Source-of-truth string from pilot.py:31
    expect(PILOT_EMAIL_RE.source).toBe("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$");
  });
});

describe("validatePilotField — name", () => {
  it("rejects empty name", () => {
    expect(validatePilotField("name", "")).toBe("Укажите имя");
    expect(validatePilotField("name", "   ")).toBe("Укажите имя");
  });

  it("rejects single-char name (matches backend min_length=2)", () => {
    // Это центральный кейс issue #1240: «Я» проходило старую проверку и упиралось в 422.
    expect(validatePilotField("name", "Я")).toBe("Минимум 2 символа");
  });

  it("accepts 2+ char name", () => {
    expect(validatePilotField("name", "Ян")).toBe("");
    expect(validatePilotField("name", "Алексей Кириллов")).toBe("");
  });

  it("rejects >200 char name", () => {
    const long = "А".repeat(201);
    expect(validatePilotField("name", long)).toBe("Максимум 200 символов");
  });

  it("accepts exactly 200 char name", () => {
    expect(validatePilotField("name", "А".repeat(200))).toBe("");
  });
});

describe("validatePilotField — phone", () => {
  it("accepts empty / short phone", () => {
    expect(validatePilotField("phone", "")).toBe("");
    expect(validatePilotField("phone", "+7 900 000-00-00")).toBe("");
  });

  it("rejects >50 char phone", () => {
    expect(validatePilotField("phone", "+".repeat(51))).toBe(
      "Максимум 50 символов",
    );
  });
});

describe("validatePilotField — email", () => {
  it("accepts empty email (optional field)", () => {
    expect(validatePilotField("email", "")).toBe("");
  });

  it("rejects malformed email", () => {
    expect(validatePilotField("email", "no-at-sign")).toBe("Некорректный email");
    expect(validatePilotField("email", "user@")).toBe("Некорректный email");
    expect(validatePilotField("email", "user@host")).toBe("Некорректный email");
    expect(validatePilotField("email", "@host.ru")).toBe("Некорректный email");
  });

  it("accepts well-formed email", () => {
    expect(validatePilotField("email", "alex@prinzip.ru")).toBe("");
    expect(validatePilotField("email", "a.b+tag@example.co.uk")).toBe("");
  });

  it("rejects >200 char email", () => {
    // 196 a-chars + "@b.ru" (5) = 201 chars total → over the limit.
    const tooLong = "a".repeat(196) + "@b.ru";
    expect(tooLong.length).toBe(201);
    expect(validatePilotField("email", tooLong)).toBe("Максимум 200 символов");
  });
});

describe("validatePilotField — company / message", () => {
  it("rejects >200 char company", () => {
    expect(validatePilotField("company", "x".repeat(201))).toBe(
      "Максимум 200 символов",
    );
  });

  it("rejects >2000 char message", () => {
    expect(validatePilotField("message", "x".repeat(2001))).toBe(
      "Максимум 2000 символов",
    );
  });
});

describe("validatePilotForm — composite", () => {
  it("returns empty object for valid form", () => {
    expect(
      validatePilotForm({
        name: "Алексей",
        phone: "+7 900 000-00-00",
        email: "alex@prinzip.ru",
        company: "PRINZIP",
        message: "Здравствуйте",
      }),
    ).toEqual({});
  });

  it("returns error map for invalid form", () => {
    const errors = validatePilotForm({
      name: "Я",
      phone: "",
      email: "bad-email",
      company: "",
      message: "",
    });
    expect(errors.name).toBe("Минимум 2 символа");
    expect(errors.email).toBe("Некорректный email");
    expect(errors.phone).toBeUndefined();
  });
});

describe("formatSubmitError — friendly 422 mapping", () => {
  it("returns generic fallback for non-Error", () => {
    expect(formatSubmitError(null)).toMatch(/Не удалось/);
    expect(formatSubmitError("string")).toMatch(/Не удалось/);
  });

  it("maps 422 to friendly text instead of raw FastAPI JSON", () => {
    // Это центральный кейс issue #1240: apiFetch кидает «API error 422: {...}»
    // и модалка раньше рендерила JSON verbatim. Теперь — человеческий текст.
    const raw = `API error 422: {"detail":[{"type":"string_too_short","loc":["body","name"],"msg":"String should have at least 2 characters","input":"Я","ctx":{"min_length":2}}]}`;
    expect(formatSubmitError(new Error(raw))).toBe(
      "Проверьте корректность заполнения полей.",
    );
  });

  it("maps 500 to friendly fallback", () => {
    expect(formatSubmitError(new Error("API error 500: boom"))).toMatch(
      /Сервер недоступен/,
    );
  });

  it("uses detail field if 4xx body is JSON with string detail", () => {
    const raw = `API error 400: {"detail":"Custom backend message"}`;
    expect(formatSubmitError(new Error(raw))).toBe("Custom backend message");
  });

  it("returns generic fallback on non-API-error message", () => {
    expect(formatSubmitError(new Error("network error"))).toMatch(/Не удалось/);
  });
});

// ── Component-level integration ─────────────────────────────────────────

function renderModal() {
  const client = new QueryClient({
    defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
  });
  return render(
    <QueryClientProvider client={client}>
      <PilotRequestModal open={true} onClose={() => {}} />
    </QueryClientProvider>,
  );
}

describe("PilotRequestModal — component behaviour", () => {
  beforeEach(() => {
    vi.spyOn(globalThis, "fetch").mockImplementation(
      () => new Promise(() => {}),
    );
  });
  afterEach(() => {
    vi.restoreAllMocks();
  });

  it("disables submit until name passes min_length", async () => {
    const user = userEvent.setup();
    renderModal();
    const submit = screen.getByRole("button", { name: /Отправить заявку/i });
    expect(submit).toBeDisabled();

    const nameInput = screen.getByLabelText(/Имя/i);
    await user.type(nameInput, "Я");
    expect(submit).toBeDisabled();

    await user.type(nameInput, "н");
    expect(submit).toBeEnabled();
  });

  it("shows inline name error on blur with 1-char name (issue #1240 trace)", async () => {
    renderModal();
    const nameInput = screen.getByLabelText(/Имя/i) as HTMLInputElement;
    fireEvent.change(nameInput, { target: { value: "Я" } });
    fireEvent.blur(nameInput);
    expect(await screen.findByText(/Минимум 2 символа/i)).toBeInTheDocument();
    // Submit should be blocked — no raw JSON 422 from backend reaches the user.
    expect(
      screen.getByRole("button", { name: /Отправить заявку/i }),
    ).toBeDisabled();
  });

  it("shows email error live when user types invalid value", async () => {
    renderModal();
    const nameInput = screen.getByLabelText(/Имя/i);
    fireEvent.change(nameInput, { target: { value: "Алексей" } });
    const emailInput = screen.getByLabelText(/Email/i) as HTMLInputElement;
    fireEvent.change(emailInput, { target: { value: "no-at" } });
    expect(await screen.findByText(/Некорректный email/i)).toBeInTheDocument();
    expect(
      screen.getByRole("button", { name: /Отправить заявку/i }),
    ).toBeDisabled();
  });

  it("sets HTML attrs mirroring schema (minLength/maxLength)", () => {
    renderModal();
    expect(screen.getByLabelText(/Имя/i)).toHaveAttribute("minLength", "2");
    expect(screen.getByLabelText(/Имя/i)).toHaveAttribute("maxLength", "200");
    expect(screen.getByLabelText(/Компания/i)).toHaveAttribute(
      "maxLength",
      "200",
    );
    expect(screen.getByLabelText(/Телефон/i)).toHaveAttribute(
      "maxLength",
      "50",
    );
    expect(screen.getByLabelText(/Email/i)).toHaveAttribute("maxLength", "200");
    expect(screen.getByLabelText(/Сообщение/i)).toHaveAttribute(
      "maxLength",
      "2000",
    );
  });
});
