"use client";

import { useState, useCallback, useEffect, useRef } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";

import { apiFetch } from "@/lib/api";

// Hand-rolled types — replace with codegen once backend job_settings endpoints
// appear in /openapi.json (run `npm run codegen` then delete this block).
interface JobSettingRead {
  job_type: string;
  enabled: boolean;
  queue_name: string;
  cron_schedule: string | null;
  rate_ms: number | null;
  max_retries: number;
  max_concurrency: number;
  extra_config: Record<string, unknown>;
  updated_at: string;
  updated_by: string | null;
  description: string | null;
}

interface JobSettingUpdate {
  enabled?: boolean | null;
  queue_name?: string | null;
  cron_schedule?: string | null;
  rate_ms?: number | null;
  max_retries?: number | null;
  max_concurrency?: number | null;
  extra_config?: Record<string, unknown> | null;
}

// Per-row draft state (mirrors JobSettingRead fields that are editable)
interface RowDraft {
  enabled: boolean;
  cron_schedule: string;
  rate_ms: string; // kept as string for controlled input, parsed on save
  max_retries: string;
  max_concurrency: string;
  extra_config_text: string; // raw textarea JSON
  extra_config_error: string | null;
}

// Toast state type
interface Toast {
  id: number;
  message: string;
  kind: "success" | "error";
}

function toRowDraft(s: JobSettingRead): RowDraft {
  return {
    enabled: s.enabled,
    cron_schedule: s.cron_schedule ?? "",
    rate_ms: s.rate_ms != null ? String(s.rate_ms) : "",
    max_retries: String(s.max_retries),
    max_concurrency: String(s.max_concurrency),
    extra_config_text: JSON.stringify(s.extra_config, null, 2),
    extra_config_error: null,
  };
}

function formatIso(s: string): string {
  return new Date(s).toLocaleString("ru-RU");
}

export default function JobSettingsPage() {
  const [token, setToken] = useState<string>(() =>
    typeof window === "undefined"
      ? ""
      : (localStorage.getItem("admin_token") ?? ""),
  );
  // Per-row drafts keyed by job_type
  const [drafts, setDrafts] = useState<Record<string, RowDraft>>({});
  // Which rows have the extra_config textarea expanded
  const [expanded, setExpanded] = useState<Record<string, boolean>>({});
  const [toasts, setToasts] = useState<Toast[]>([]);
  const toastSeqRef = useRef(0);
  const queryClient = useQueryClient();

  const saveToken = (v: string) => {
    setToken(v);
    if (typeof window !== "undefined") {
      localStorage.setItem("admin_token", v);
    }
  };

  const addToast = useCallback((message: string, kind: "success" | "error") => {
    const id = ++toastSeqRef.current;
    setToasts((prev: Toast[]) => [...prev, { id, message, kind }]);
    window.setTimeout(
      () =>
        setToasts((prev: Toast[]) => prev.filter((t: Toast) => t.id !== id)),
      4000,
    );
  }, []);

  const settings = useQuery({
    queryKey: ["job-settings", token],
    queryFn: () =>
      apiFetch<JobSettingRead[]>("/api/v1/admin/jobs/settings", {
        headers: { "X-Admin-Token": token },
      }),
    enabled: !!token,
  });

  // Initialise drafts when settings data first arrives (or new job_types appear).
  // Using useEffect for syncing external data → local draft state, per codebase pattern.
  useEffect(() => {
    if (!settings.data) return;
    setDrafts((prev: Record<string, RowDraft>) => {
      const next = { ...prev };
      let changed = false;
      for (const s of settings.data) {
        if (!(s.job_type in next)) {
          next[s.job_type] = toRowDraft(s);
          changed = true;
        }
      }
      return changed ? next : prev;
    });
  }, [settings.data]);

  const updateMutation = useMutation({
    mutationFn: ({
      job_type,
      update,
    }: {
      job_type: string;
      update: JobSettingUpdate;
    }) =>
      apiFetch<JobSettingRead>(`/api/v1/admin/jobs/settings/${job_type}`, {
        method: "PUT",
        headers: { "X-Admin-Token": token },
        body: JSON.stringify(update),
      }),
    onSuccess: (data: JobSettingRead) => {
      setDrafts((prev: Record<string, RowDraft>) => ({
        ...prev,
        [data.job_type]: toRowDraft(data),
      }));
      void queryClient.invalidateQueries({ queryKey: ["job-settings"] });
      addToast(`${data.job_type}: сохранено`, "success");
    },
    onError: (
      err: unknown,
      variables: { job_type: string; update: JobSettingUpdate },
    ) => {
      addToast(
        `${variables.job_type}: ошибка — ${(err as Error).message}`,
        "error",
      );
    },
  });

  const patchDraft = (job_type: string, patch: Partial<RowDraft>) => {
    setDrafts((prev: Record<string, RowDraft>) => ({
      ...prev,
      [job_type]: { ...(prev[job_type] ?? {}), ...patch } as RowDraft,
    }));
  };

  const handleSave = (job_type: string) => {
    const draft = drafts[job_type];
    if (!draft) return;

    // Parse extra_config JSON only when the textarea is expanded
    let extra_config: Record<string, unknown> | undefined;
    if (expanded[job_type]) {
      try {
        const parsed = JSON.parse(draft.extra_config_text) as unknown;
        if (
          typeof parsed !== "object" ||
          parsed === null ||
          Array.isArray(parsed)
        ) {
          patchDraft(job_type, {
            extra_config_error: "Должен быть JSON-объект (не массив и не null)",
          });
          return;
        }
        extra_config = parsed as Record<string, unknown>;
        patchDraft(job_type, { extra_config_error: null });
      } catch (e) {
        patchDraft(job_type, {
          extra_config_error: `Невалидный JSON: ${(e as Error).message}`,
        });
        return;
      }
    }

    const update: JobSettingUpdate = {
      enabled: draft.enabled,
      cron_schedule: draft.cron_schedule.trim() || null,
      rate_ms: draft.rate_ms !== "" ? Number(draft.rate_ms) : null,
      max_retries: Number(draft.max_retries),
      max_concurrency: Number(draft.max_concurrency),
    };
    if (extra_config !== undefined) {
      update.extra_config = extra_config;
    }

    updateMutation.mutate({ job_type, update });
  };

  const isPendingFor = (job_type: string) =>
    updateMutation.isPending &&
    (
      updateMutation.variables as
        | { job_type: string; update: JobSettingUpdate }
        | undefined
    )?.job_type === job_type;

  return (
    <>
      {/* Toast notifications */}
      <div
        style={{
          position: "fixed",
          top: 20,
          right: 20,
          zIndex: 9999,
          display: "flex",
          flexDirection: "column",
          gap: 8,
        }}
      >
        {toasts.map((t: Toast) => (
          <div
            key={t.id}
            style={{
              padding: "10px 16px",
              borderRadius: 8,
              fontSize: 13,
              fontWeight: 500,
              background: t.kind === "success" ? "#f0fdf4" : "#fef2f2",
              border: `1px solid ${t.kind === "success" ? "#bbf7d0" : "#fecaca"}`,
              color: t.kind === "success" ? "#065f46" : "#991b1b",
              boxShadow: "0 2px 8px rgba(0,0,0,.08)",
              maxWidth: 360,
              wordBreak: "break-word",
            }}
          >
            {t.kind === "success" ? "✓ " : "✕ "}
            {t.message}
          </div>
        ))}
      </div>

      <h2 style={{ marginTop: 0, fontSize: 18 }}>Job Settings</h2>
      <p style={{ color: "#5b6066", fontSize: 13, marginTop: 4 }}>
        Централизованные настройки Celery jobs. Изменения применяются с
        следующего beat-цикла. Manual trigger остаётся в{" "}
        <a
          href="/admin/scrape"
          style={{ color: "#1d4ed8", textDecoration: "none" }}
        >
          /admin/scrape
        </a>
        .
      </p>

      {/* Token */}
      <section style={cardStyle}>
        <label style={{ display: "block" }}>
          <span style={labelStyle}>Admin Token (X-Admin-Token)</span>
          <input
            type="password"
            value={token}
            onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
              saveToken(e.target.value)
            }
            placeholder="SCRAPE_ADMIN_TOKEN — сохранится в localStorage"
            style={{ ...inputStyle, maxWidth: 400 }}
          />
        </label>
      </section>

      {/* Table */}
      <section style={{ ...cardStyle, marginTop: 16 }}>
        {!token ? (
          <p style={{ color: "#5b6066", fontSize: 13 }}>
            Введите Admin Token выше чтобы загрузить настройки.
          </p>
        ) : settings.isLoading ? (
          <p style={{ color: "#5b6066" }}>Загрузка…</p>
        ) : settings.isError ? (
          <p style={{ color: "#b3261e" }}>Ошибка: {String(settings.error)}</p>
        ) : (
          <div style={{ overflowX: "auto" }}>
            <table
              style={{
                width: "100%",
                borderCollapse: "collapse",
                fontSize: 13,
              }}
            >
              <thead>
                <tr style={{ background: "#f6f7f9" }}>
                  {[
                    "job_type",
                    "enabled",
                    "queue_name",
                    "cron_schedule",
                    "rate_ms",
                    "max_retries",
                    "max_concurrency",
                    "extra_config",
                    "updated_at",
                    "",
                  ].map((h) => (
                    <th key={h} style={th}>
                      {h}
                    </th>
                  ))}
                </tr>
              </thead>
              <tbody>
                {(settings.data ?? []).map((row: JobSettingRead) => {
                  const draft = drafts[row.job_type];
                  if (!draft) return null;
                  const isExpanded = expanded[row.job_type] ?? false;
                  const saving = isPendingFor(row.job_type);

                  return (
                    <tr
                      key={row.job_type}
                      style={{
                        borderBottom: "1px solid #eef0f3",
                        verticalAlign: "top",
                        opacity: saving ? 0.6 : 1,
                        transition: "opacity .15s",
                      }}
                    >
                      {/* job_type — read-only */}
                      <td style={{ ...td, fontWeight: 600 }}>
                        <code
                          style={{
                            fontFamily: "ui-monospace,SFMono-Regular,monospace",
                            background: "#f3f4f6",
                            padding: "2px 6px",
                            borderRadius: 4,
                          }}
                        >
                          {row.job_type}
                        </code>
                        {row.description && (
                          <div
                            style={{
                              fontSize: 11,
                              color: "#6b7280",
                              marginTop: 4,
                              maxWidth: 160,
                            }}
                          >
                            {row.description}
                          </div>
                        )}
                      </td>

                      {/* enabled — checkbox */}
                      <td style={{ ...td, textAlign: "center" }}>
                        <input
                          type="checkbox"
                          checked={draft.enabled}
                          onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                            patchDraft(row.job_type, {
                              enabled: e.target.checked,
                            })
                          }
                          style={{ width: 16, height: 16, cursor: "pointer" }}
                        />
                      </td>

                      {/* queue_name — read-only (infra config) */}
                      <td style={{ ...td, color: "#6b7280" }}>
                        <code style={{ fontSize: 12 }}>{row.queue_name}</code>
                      </td>

                      {/* cron_schedule */}
                      <td style={td}>
                        <input
                          type="text"
                          value={draft.cron_schedule}
                          onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                            patchDraft(row.job_type, {
                              cron_schedule: e.target.value,
                            })
                          }
                          placeholder="* * * * *"
                          style={{
                            ...compactInput,
                            fontFamily: "ui-monospace,SFMono-Regular,monospace",
                            width: 130,
                          }}
                        />
                      </td>

                      {/* rate_ms */}
                      <td style={td}>
                        <input
                          type="number"
                          value={draft.rate_ms}
                          min={0}
                          onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                            patchDraft(row.job_type, {
                              rate_ms: e.target.value,
                            })
                          }
                          placeholder="—"
                          style={{ ...compactInput, width: 80 }}
                        />
                      </td>

                      {/* max_retries */}
                      <td style={td}>
                        <input
                          type="number"
                          value={draft.max_retries}
                          min={0}
                          max={10}
                          onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                            patchDraft(row.job_type, {
                              max_retries: e.target.value,
                            })
                          }
                          style={{ ...compactInput, width: 60 }}
                        />
                      </td>

                      {/* max_concurrency */}
                      <td style={td}>
                        <input
                          type="number"
                          value={draft.max_concurrency}
                          min={1}
                          max={20}
                          onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
                            patchDraft(row.job_type, {
                              max_concurrency: e.target.value,
                            })
                          }
                          style={{ ...compactInput, width: 60 }}
                        />
                      </td>

                      {/* extra_config — collapsible textarea */}
                      <td style={td}>
                        <button
                          type="button"
                          onClick={() =>
                            setExpanded((prev: Record<string, boolean>) => ({
                              ...prev,
                              [row.job_type]: !isExpanded,
                            }))
                          }
                          style={toggleBtn}
                        >
                          {isExpanded ? "▼ скрыть" : "▶ JSON"}
                        </button>
                        {isExpanded && (
                          <div style={{ marginTop: 6 }}>
                            <textarea
                              value={draft.extra_config_text}
                              onChange={(
                                e: React.ChangeEvent<HTMLTextAreaElement>,
                              ) => {
                                patchDraft(row.job_type, {
                                  extra_config_text: e.target.value,
                                  extra_config_error: null,
                                });
                              }}
                              rows={6}
                              style={{
                                width: 280,
                                fontFamily:
                                  "ui-monospace,SFMono-Regular,monospace",
                                fontSize: 11,
                                padding: "6px 8px",
                                border: draft.extra_config_error
                                  ? "1px solid #fca5a5"
                                  : "1px solid #d1d5db",
                                borderRadius: 6,
                                resize: "vertical",
                                boxSizing: "border-box",
                              }}
                              spellCheck={false}
                            />
                            {draft.extra_config_error && (
                              <div
                                style={{
                                  color: "#b3261e",
                                  fontSize: 11,
                                  marginTop: 2,
                                }}
                              >
                                {draft.extra_config_error}
                              </div>
                            )}
                          </div>
                        )}
                      </td>

                      {/* updated_at — read-only */}
                      <td
                        style={{
                          ...td,
                          color: "#6b7280",
                          fontSize: 11,
                          whiteSpace: "nowrap",
                        }}
                      >
                        {formatIso(row.updated_at)}
                        {row.updated_by && (
                          <div style={{ color: "#9ca3af" }}>
                            {row.updated_by}
                          </div>
                        )}
                      </td>

                      {/* Save button */}
                      <td style={{ ...td, whiteSpace: "nowrap" }}>
                        <button
                          type="button"
                          onClick={() => handleSave(row.job_type)}
                          disabled={saving || !token}
                          style={
                            saving ? { ...saveBtn, opacity: 0.6 } : saveBtn
                          }
                        >
                          {saving ? "…" : "Сохранить"}
                        </button>
                      </td>
                    </tr>
                  );
                })}
              </tbody>
            </table>
          </div>
        )}
      </section>
    </>
  );
}

// ── Styles ──────────────────────────────────────────────────────────────────

const cardStyle = {
  background: "#fff",
  border: "1px solid #e6e8ec",
  borderRadius: 12,
  padding: 20,
};

const labelStyle = {
  display: "block",
  fontSize: 12,
  color: "#5b6066",
  marginBottom: 4,
  textTransform: "uppercase" as const,
  letterSpacing: 0.4,
};

const inputStyle = {
  padding: "8px 10px",
  border: "1px solid #d1d5db",
  borderRadius: 6,
  fontSize: 14,
  width: "100%",
  boxSizing: "border-box" as const,
};

const compactInput = {
  padding: "6px 8px",
  border: "1px solid #d1d5db",
  borderRadius: 6,
  fontSize: 13,
  boxSizing: "border-box" as const,
};

const th = {
  padding: "8px 10px",
  textAlign: "left" as const,
  fontWeight: 600 as const,
  borderBottom: "1px solid #e6e8ec",
  whiteSpace: "nowrap" as const,
};

const td = { padding: "8px 10px" };

const saveBtn = {
  padding: "6px 14px",
  background: "#1d4ed8",
  color: "#fff",
  border: "none",
  borderRadius: 6,
  fontSize: 13,
  fontWeight: 600 as const,
  cursor: "pointer",
};

const toggleBtn = {
  padding: "3px 8px",
  background: "#f3f4f6",
  color: "#374151",
  border: "1px solid #d1d5db",
  borderRadius: 4,
  fontSize: 11,
  cursor: "pointer",
  whiteSpace: "nowrap" as const,
};
