import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { ApiError } from "../data/live/http";
import { KEYS } from "../data/mock/keys";
import { I18nProvider } from "../i18n/I18nContext";
import { translate } from "../lib/i18n/messages";
import type { RadarWatch, WatchTermSuggestion } from "../domain/types";
import { RadarWatchesPage } from "./RadarWatchesPage";

const t = (key: string, params?: Record<string, string | number>) => translate("zh-TW", key, params);

const backend = vi.hoisted(() => ({
  watches: [] as RadarWatch[],
  profileExists: true,
  maxActive: 5,
  suggestions: [] as WatchTermSuggestion[],
  suggestError: null as unknown,
  seq: 0,
  hours: [6] as number[],
}));

function activeCount(): number {
  return backend.watches.filter((w) => w.status === "active").length;
}

function find(id: string): RadarWatch {
  const w = backend.watches.find((x) => x.id === id);
  if (!w) throw new ApiError("watch not found", 404001, 404);
  return w;
}

vi.mock("../data/DataContext", () => {
  const repos = {
    radar: {
      async listWatches(page = 1, pageSize = 20, status?: string) {
        const all = backend.watches.filter((w) => !status || w.status === status);
        return {
          list: all.slice((page - 1) * pageSize, page * pageSize),
          total: all.length,
          active_count: activeCount(),
          max_active: backend.maxActive,
          profile_exists: backend.profileExists,
        };
      },
      async createWatch(input: {
        terms: string[];
        exclude_terms?: string[];
        regions?: string[];
        enabled?: boolean;
      }) {
        const wantsActive = input.enabled !== false;
        if (wantsActive && activeCount() >= backend.maxActive) {
          throw new ApiError(
            `active watch limit reached (${backend.maxActive} of ${backend.maxActive} on your plan); pause an existing watch or upgrade your plan`,
            400100,
            400,
          );
        }
        backend.seq += 1;
        const w: RadarWatch = {
          id: `w${backend.seq}`,
          terms: input.terms,
          exclude_terms: input.exclude_terms ?? [],
          regions: input.regions ?? [],
          status: wantsActive ? "active" : "paused",
          created_at: 1,
          updated_at: 1,
        };
        backend.watches.push(w);
        return w;
      },
      async updateWatch(id: string, patch: { terms?: string[]; exclude_terms?: string[]; regions?: string[] }) {
        const w = find(id);
        Object.assign(w, patch);
        return w;
      },
      async pauseWatch(id: string) {
        const w = find(id);
        w.status = "paused";
        return w;
      },
      async resumeWatch(id: string) {
        const w = find(id);
        if (activeCount() >= backend.maxActive) {
          throw new ApiError(
            `active watch limit reached (${backend.maxActive} of ${backend.maxActive} on your plan); pause an existing watch or upgrade your plan`,
            400100,
            400,
          );
        }
        w.status = "active";
        return w;
      },
      async archiveWatch(id: string) {
        find(id).status = "archived";
      },
      async deleteArchivedWatch(id: string) {
        const w = find(id);
        if (w.status !== "archived") throw new ApiError("only archived watch can be permanently deleted", 400100, 400);
        backend.watches = backend.watches.filter((row) => row.id !== id);
      },
      async suggestWatchTerms(): Promise<WatchTermSuggestion[]> {
        if (backend.suggestError) throw backend.suggestError;
        return backend.suggestions;
      },
      async getRadarSchedule() {
        return { hours: backend.hours, timezone: "Asia/Taipei" };
      },
      async saveRadarSchedule(hours: number[]) {
        backend.hours = [...hours].sort((a, b) => a - b);
        return { hours: backend.hours, timezone: "Asia/Taipei" };
      },
    },
  };
  return { useRepos: () => repos };
});

function renderPage() {
  return render(
    <I18nProvider>
      <MemoryRouter>
        <RadarWatchesPage />
      </MemoryRouter>
    </I18nProvider>,
  );
}

function row(term: string): HTMLElement {
  const chip = screen.getByText(term);
  const article = chip.closest("article");
  if (!article) throw new Error(`no watch row for ${term}`);
  return article;
}

async function createWatch(terms: string) {
  fireEvent.click(screen.getByRole("button", { name: t("radar.watches.add") }));
  fireEvent.change(screen.getByLabelText(t("radar.watches.terms"), { exact: false }), {
    target: { value: terms },
  });
  fireEvent.click(screen.getByRole("button", { name: t("common.save") }));
}

beforeEach(() => {
  localStorage.setItem(
    KEYS.uiPrefs,
    JSON.stringify({ locale: "zh-TW", currency: "TWD", theme: "system" }),
  );
  backend.watches = [];
  backend.profileExists = true;
  backend.maxActive = 5;
  backend.suggestions = [];
  backend.suggestError = null;
  backend.seq = 0;
  backend.hours = [6];
  vi.spyOn(window, "confirm").mockReturnValue(true);
});

describe("RadarWatchesPage", () => {
  it("建立表單直接標示關鍵字為必填", async () => {
    renderPage();
    await screen.findByText(t("radar.watches.empty"));

    fireEvent.click(screen.getByRole("button", { name: t("radar.watches.add") }));

    const terms = screen.getByLabelText(t("radar.watches.terms"), { exact: false }) as HTMLTextAreaElement;
    expect(terms.required).toBe(true);
    expect(terms.closest("label")?.querySelector(".hb-field__required")?.textContent).toBe("*");
    expect(screen.getByText(t("radar.watches.requiredHint"))).toBeTruthy();
  });

  it("沒服務檔案也能新增，品牌只是選項", async () => {
    backend.profileExists = false;
    renderPage();

    expect(await screen.findByText(t("radar.watches.profileOptional"))).toBeTruthy();
    expect(screen.getByRole("button", { name: t("radar.watches.add") })).not.toBeDisabled();
    fireEvent.click(screen.getByRole("button", { name: t("radar.watches.add") }));
    fireEvent.change(screen.getByLabelText(t("radar.watches.terms"), { exact: false }), {
      target: { value: "台北 美甲推薦" },
    });
    fireEvent.click(screen.getByRole("button", { name: t("common.save") }));
    await screen.findByText(t("radar.watches.created"));
    expect(screen.getByText("台北 美甲推薦")).toBeTruthy();
  });

  it("建立→暫停→恢復→封存的狀態變化都反映在列上", async () => {
    renderPage();
    await screen.findByText(t("radar.watches.empty"));

    await createWatch("室內設計\n找設計師");
    await screen.findByText(t("radar.watches.created"));
    expect(within(row("室內設計")).getByText(t("radar.watches.status.active"))).toBeTruthy();
    expect(screen.getByText(t("radar.watches.quota", { used: 1, max: 5 }))).toBeTruthy();

    fireEvent.click(within(row("室內設計")).getByRole("button", { name: t("radar.watches.pause") }));
    await screen.findByText(t("radar.watches.paused"));
    expect(within(row("室內設計")).getByText(t("radar.watches.status.paused"))).toBeTruthy();
    expect(screen.getByText(t("radar.watches.quota", { used: 0, max: 5 }))).toBeTruthy();

    fireEvent.click(within(row("室內設計")).getByRole("button", { name: t("radar.watches.resume") }));
    await screen.findByText(t("radar.watches.resumed"));
    expect(within(row("室內設計")).getByText(t("radar.watches.status.active"))).toBeTruthy();

    fireEvent.click(within(row("室內設計")).getByRole("button", { name: t("radar.watches.archive") }));
    await screen.findByText(t("radar.watches.archived"));
    const archived = row("室內設計");
    expect(within(archived).getByText(t("radar.watches.status.archived"))).toBeTruthy();
    // 封存後只能刪除設定；既有歷史資料不會被這個動作連帶刪除。
    const deleteButton = within(archived).getByRole("button", { name: t("radar.watches.deleteArchived") });
    fireEvent.click(deleteButton);
    await screen.findByText(t("radar.watches.deletedArchived"));
    expect(screen.queryByText("室內設計")).toBeNull();
  });

  it("配額滿時照後端訊息說上限與升級，表單留著讓人改", async () => {
    backend.maxActive = 1;
    renderPage();
    await screen.findByText(t("radar.watches.empty"));

    await createWatch("室內設計");
    await screen.findByText(t("radar.watches.created"));
    expect(screen.getByText(t("radar.watches.quotaFull"), { exact: false })).toBeTruthy();

    await createWatch("找設計師");
    const banner = await screen.findByRole("alert");
    expect(banner.textContent).toContain("active watch limit reached (1 of 1 on your plan)");
    // 表單沒被關掉：使用者可以改成不立刻啟用再送
    expect(screen.getByText(t("radar.watches.newTitle"))).toBeTruthy();
  });

  it("關鍵字建議可逐條採用，include 進關鍵字、exclude 進排除詞", async () => {
    backend.suggestions = [
      { term: "室內設計", reason: "客人找設計師時最常這樣問", usage: "include" },
      { term: "徵才", reason: "這類是招募文，不是客人", usage: "exclude" },
    ];
    renderPage();
    await screen.findByText(t("radar.watches.empty"));
    fireEvent.click(screen.getByRole("button", { name: t("radar.watches.add") }));
    fireEvent.click(screen.getByRole("button", { name: t("radar.watches.showAdvanced") }));
    fireEvent.click(screen.getByRole("button", { name: t("radar.suggest.ask") }));

    await screen.findByText("客人找設計師時最常這樣問");
    const adoptButtons = screen.getAllByRole("button", { name: t("radar.suggest.adopt") });
    fireEvent.click(adoptButtons[0]);
    fireEvent.click(adoptButtons[1]);

    expect(
      (screen.getByLabelText(t("radar.watches.terms"), { exact: false }) as HTMLTextAreaElement).value,
    ).toBe("室內設計");
    expect(
      (screen.getByLabelText(t("radar.watches.excludeTerms"), { exact: false }) as HTMLTextAreaElement)
        .value,
    ).toBe("徵才");
    // 排除詞在進階區裡，採用後不能把使用者的輸入藏回去
    expect(screen.getByRole("button", { name: t("radar.watches.hideAdvanced") })).toBeTruthy();
    // 已採用的不能再按，否則使用者會以為沒生效而重複點
    await waitFor(() =>
      expect(screen.getAllByRole("button", { name: t("radar.suggest.adopted") })).toHaveLength(2),
    );
  });

  it("建議失敗時說出原因，不裝作沒有建議", async () => {
    backend.suggestError = new ApiError(
      "AI 沒有回傳可用的關鍵字建議，請稍後再試",
      400100,
      400,
    );
    renderPage();
    await screen.findByText(t("radar.watches.empty"));
    fireEvent.click(screen.getByRole("button", { name: t("radar.watches.add") }));
    fireEvent.click(screen.getByRole("button", { name: t("radar.watches.showAdvanced") }));
    fireEvent.click(screen.getByRole("button", { name: t("radar.suggest.ask") }));

    const banner = await screen.findByRole("alert");
    expect(banner.textContent).toContain("AI 沒有回傳可用的關鍵字建議");
    expect(screen.queryByText(t("radar.suggest.none"))).toBeNull();
  });

  it("長句關鍵字儲存時收成可搜短詞", async () => {
    renderPage();
    await screen.findByText(t("radar.watches.empty"));
    await createWatch("晚上睡覺容易口乾舌燥怎麼辦");
    await screen.findByText(t("radar.watches.created"));
    expect(screen.getByText("晚上睡覺")).toBeTruthy();
    expect(screen.getByText("口乾舌燥")).toBeTruthy();
  });

  it("可以多選巡邏時段", async () => {
    renderPage();
    const evening = await screen.findByRole("button", { name: t("radar.watches.slot.18") });
    expect(screen.getByRole("button", { name: t("radar.watches.slot.6") })).toHaveAttribute("aria-pressed", "true");
    fireEvent.click(evening);
    await screen.findByText(t("radar.watches.scheduleSaved"));
    expect(backend.hours).toEqual([6, 18]);
    expect(evening).toHaveAttribute("aria-pressed", "true");
  });
});
