import { useMemo, useState } from "react";
import clsx from "clsx";

import {
  useAdminUploads,
  type AdminUploadsContent,
  type AdminUploadsContentFlags,
} from "~/shared/services/admin";
import { Section, Badge, InfoRow } from "../components";
import { useAdminContext } from "../context";
import { formatBytes, formatDate, numberFormatter } from "../utils/format";

type UploadCategory = "issues" | "processing" | "ready" | "unindexed";
type UploadFilter = "all" | UploadCategory;

type UploadDecoration = {
  item: AdminUploadsContent;
  categories: Set<UploadCategory>;
  searchText: string;
  hasIssue: boolean;
  isReady: boolean;
  isProcessing: boolean;
  isUnindexed: boolean;
  flags: AdminUploadsContentFlags | null;
};

const numberCompact = new Intl.NumberFormat("ru-RU", { notation: "compact" });

const computeFlags = (item: AdminUploadsContent): AdminUploadsContentFlags => {
  const normalize = (value: string | null | undefined) => (value ?? "").toLowerCase();
  const uploadState = normalize(item.status.upload_state);
  const conversionState = normalize(item.status.conversion_state);
  const ipfsState = normalize(item.status.ipfs_state);
  const pinState = normalize(item.ipfs?.pin_state);
  const derivativeStates = item.derivatives.map((derivative) => normalize(derivative.status));
  const statusValues = [uploadState, conversionState, ipfsState, pinState, ...derivativeStates];

  const hasIssue =
    statusValues.some((value) => value.includes("fail") || value.includes("error") || value.includes("timeout")) ||
    item.upload_history.some((event) => Boolean(event.error)) ||
    item.derivatives.some((derivative) => Boolean(derivative.error)) ||
    Boolean(item.ipfs?.pin_error);

  const isOnchainIndexed = item.status.onchain?.indexed ?? false;
  const isUnindexed = !isOnchainIndexed;

  const conversionDone =
    conversionState.includes("converted") ||
    conversionState.includes("ready") ||
    derivativeStates.some((state) => state.includes("ready") || state.includes("converted") || state.includes("complete"));

  const ipfsDone =
    ipfsState.includes("pinned") ||
    ipfsState.includes("ready") ||
    pinState.includes("pinned") ||
    pinState.includes("ready");

  const isReady = !hasIssue && conversionDone && ipfsDone && isOnchainIndexed;

  const hasProcessingKeywords =
    uploadState.includes("pending") ||
    uploadState.includes("process") ||
    uploadState.includes("queue") ||
    uploadState.includes("upload") ||
    conversionState.includes("pending") ||
    conversionState.includes("process") ||
    conversionState.includes("queue") ||
    conversionState.includes("convert") ||
    ipfsState.includes("pin") ||
    ipfsState.includes("sync") ||
    pinState.includes("pin") ||
    pinState.includes("sync") ||
    derivativeStates.some((state) => state.includes("pending") || state.includes("process") || state.includes("queue"));

  const isProcessingCandidate = !isReady && !hasIssue && hasProcessingKeywords;
  const isProcessing = isProcessingCandidate || (!isReady && !hasIssue && !isProcessingCandidate);

  return {
    issues: hasIssue,
    processing: isProcessing,
    ready: isReady,
    unindexed: isUnindexed,
  };
};

const classifyUpload = (item: AdminUploadsContent): UploadDecoration => {
  const flags = item.flags ?? computeFlags(item);
  const categories = new Set<UploadCategory>();
  if (flags.issues) {
    categories.add("issues");
  }
  if (flags.processing) {
    categories.add("processing");
  }
  if (flags.ready) {
    categories.add("ready");
  }
  if (flags.unindexed) {
    categories.add("unindexed");
  }
  if (categories.size === 0) {
    if (!(item.status.onchain?.indexed ?? false)) {
      categories.add("unindexed");
    }
    categories.add("processing");
  }

  const searchParts: Array<string | number | null | undefined> = [
    item.title,
    item.description,
    item.encrypted_cid,
    item.metadata_cid,
    item.content_hash,
    item.status.onchain?.item_address,
    item.stored?.owner_address,
  ];
  if (item.stored?.user) {
    const user = item.stored.user;
    searchParts.push(user.id, user.telegram_id, user.username, user.first_name, user.last_name);
  }
  const searchText = searchParts
    .filter((value) => value !== null && value !== undefined && `${value}`.length > 0)
    .map((value) => `${value}`.toLowerCase())
    .join(" ");

  return {
    item,
    categories,
    searchText,
    hasIssue: Boolean(flags.issues),
    isReady: Boolean(flags.ready),
    isProcessing: Boolean(flags.processing),
    isUnindexed: Boolean(flags.unindexed),
    flags: item.flags ?? flags,
  };
};

const getProblemMessages = (item: AdminUploadsContent): string[] => {
  const messages: string[] = [];
  for (let index = item.upload_history.length - 1; index >= 0; index -= 1) {
    const event = item.upload_history[index];
    if (event.error) {
      messages.push(`Загрузка: ${event.error}`);
      break;
    }
  }
  const derivativeError = item.derivatives.find((derivative) => Boolean(derivative.error));
  if (derivativeError?.error) {
    messages.push(`Дериватив ${derivativeError.kind}: ${derivativeError.error}`);
  }
  if (item.ipfs?.pin_error) {
    messages.push(`IPFS: ${item.ipfs.pin_error}`);
  }
  const conversionState = item.status.conversion_state;
  if (!messages.length && conversionState && conversionState.toLowerCase().includes("fail")) {
    messages.push(`Конверсия: ${conversionState}`);
  }
  const uploadState = item.status.upload_state;
  if (!messages.length && uploadState && uploadState.toLowerCase().includes("fail")) {
    messages.push(`Загрузка: ${uploadState}`);
  }
  return messages;
};

const UploadCard = ({ decoration }: { decoration: UploadDecoration }) => {
  const { item, flags } = decoration;
  const problemMessages = decoration.hasIssue ? getProblemMessages(item) : [];
  const derivativeDownloads = item.links.download_derivatives ?? [];

  return (
    <div className="rounded-2xl border border-slate-800 bg-slate-950/40 p-5 shadow-inner shadow-black/40">
      <div className="flex flex-wrap items-start justify-between gap-3">
        <div>
          <h3 className="text-lg font-semibold text-slate-100">{item.title || "Без названия"}</h3>
          <p className="text-xs text-slate-400">{item.description || "Описание не задано"}</p>
          <div className="mt-2 flex flex-wrap items-center gap-2 text-[11px] uppercase tracking-wide text-slate-500">
            {flags?.ready ? <Badge tone="success">готово</Badge> : null}
            {flags?.issues ? <Badge tone="danger">проблемы</Badge> : null}
            {flags?.processing ? <Badge tone="warn">в обработке</Badge> : null}
            {flags?.unindexed ? <Badge tone="neutral">без индекса</Badge> : null}
          </div>
        </div>
        <div className="flex flex-col items-end gap-1 text-xs text-slate-400">
          <span>{formatDate(item.created_at)}</span>
          <span>{formatDate(item.updated_at)}</span>
          {item.size.plain ? <span>{formatBytes(item.size.plain)}</span> : null}
        </div>
      </div>

      <div className="mt-4 grid gap-4 lg:grid-cols-[1.3fr_1fr]">
        <div className="space-y-4">
          <div className="rounded-xl bg-slate-900/60 p-4 ring-1 ring-slate-800">
            <h4 className="text-xs font-semibold uppercase tracking-wide text-slate-500">On-chain</h4>
            <div className="mt-2 grid gap-2 text-xs text-slate-300 sm:grid-cols-2">
              <InfoRow label="Encrypted CID">
                <span className="break-all font-mono">{item.encrypted_cid}</span>
              </InfoRow>
              <InfoRow label="Metadata CID">
                <span className="break-all font-mono">{item.metadata_cid ?? "—"}</span>
              </InfoRow>
              <InfoRow label="Content hash">
                <span className="break-all font-mono">{item.content_hash ?? "—"}</span>
              </InfoRow>
              <InfoRow label="On-chain индекс">
                {item.status.onchain?.indexed ? (
                  <span className="inline-flex items-center gap-2">
                    <Badge tone="success">indexed</Badge>
                    {item.status.onchain?.onchain_index ?? "—"}
                  </span>
                ) : (
                  <Badge tone="warn">не опубликовано</Badge>
                )}
              </InfoRow>
              <InfoRow label="On-chain адрес">
                <span className="break-all font-mono text-xs">{item.status.onchain?.item_address ?? "—"}</span>
              </InfoRow>
            </div>
          </div>

          <div className="rounded-xl bg-slate-900/60 p-4 ring-1 ring-slate-800">
            <h4 className="text-xs font-semibold uppercase tracking-wide text-slate-500">Статусы</h4>
            <div className="mt-2 grid gap-3 text-xs text-slate-300 md:grid-cols-2">
              <div>
                <p className="font-semibold text-slate-200">Загрузка</p>
                <p>{item.status.upload_state ?? "—"}</p>
              </div>
              <div>
                <p className="font-semibold text-slate-200">Конверсия</p>
                <p>{item.status.conversion_state ?? "—"}</p>
              </div>
              <div>
                <p className="font-semibold text-slate-200">IPFS</p>
                <p>{item.status.ipfs_state ?? "—"}</p>
              </div>
            </div>
          </div>

          <div className="rounded-xl bg-slate-900/60 p-4 ring-1 ring-slate-800">
            <h4 className="text-xs font-semibold uppercase tracking-wide text-slate-500">История загрузки</h4>
            <ul className="mt-2 space-y-2 text-xs text-slate-300">
              {item.upload_history.map((event) => (
                <li key={`${item.encrypted_cid}-${event.filename}-${event.state}`} className="rounded-lg border border-slate-800 bg-slate-950/40 px-3 py-2">
                  <div className="flex flex-wrap items-center justify-between gap-2">
                    <span className="font-semibold text-slate-200">{event.state}</span>
                    <span className="text-[11px] text-slate-500">{formatDate(event.at)}</span>
                  </div>
                  {event.error ? <p className="mt-1 text-rose-300">{event.error}</p> : null}
                  {event.filename ? <p className="mt-1 text-slate-400">{event.filename}</p> : null}
                </li>
              ))}
            </ul>
          </div>

          {problemMessages.length > 0 ? (
            <div className="rounded-xl border border-rose-500/40 bg-rose-500/10 p-4 text-xs text-rose-100">
              <h4 className="font-semibold uppercase tracking-wide">Проблемы</h4>
              <ul className="mt-2 space-y-1">
                {problemMessages.map((message, index) => (
                  <li key={`${item.encrypted_cid}-issue-${index}`}>{message}</li>
                ))}
              </ul>
            </div>
          ) : null}
        </div>

        <div className="space-y-4">
          <div className="rounded-xl bg-slate-900/40 p-4 ring-1 ring-slate-800">
            <h4 className="text-xs font-semibold uppercase tracking-wide text-slate-500">Деривативы</h4>
            <ul className="mt-2 space-y-2 text-xs text-slate-300">
              {item.derivatives.map((derivative) => (
                <li key={`${item.encrypted_cid}-${derivative.kind}`} className="rounded-lg border border-slate-800 bg-slate-950/40 px-3 py-2">
                  <div className="flex items-center justify-between">
                    <span className="font-semibold text-slate-200">{derivative.kind}</span>
                    <Badge tone={derivative.status === "ready" ? "success" : derivative.status === "failed" ? "danger" : "warn"}>
                      {derivative.status}
                    </Badge>
                  </div>
                  <div className="mt-1 space-y-1 text-[11px] text-slate-400">
                    <div>Размер: {formatBytes(derivative.size_bytes)}</div>
                    <div>Попытки: {derivative.attempts}</div>
                    <div>Создан: {formatDate(derivative.created_at)}</div>
                    <div>Обновлён: {formatDate(derivative.updated_at)}</div>
                    {derivative.error ? <div className="text-rose-300">{derivative.error}</div> : null}
                    {derivative.download_url ? (
                      <a
                        href={derivative.download_url}
                        target="_blank"
                        rel="noreferrer"
                        className="inline-flex items-center text-sky-300 hover:text-sky-200"
                      >
                        Скачать
                      </a>
                    ) : null}
                  </div>
                </li>
              ))}
            </ul>
          </div>

          <div className="rounded-xl bg-slate-900/40 p-4 ring-1 ring-slate-800">
            <h4 className="text-xs font-semibold uppercase tracking-wide text-slate-500">IPFS</h4>
            {item.ipfs ? (
              <div className="mt-2 text-xs text-slate-300">
                <div>Статус: {item.ipfs.pin_state ?? "—"}</div>
                <div>Ошибка: {item.ipfs.pin_error ?? "—"}</div>
                <div>Fetched: {formatBytes(item.ipfs.bytes_fetched)}</div>
                <div>Total: {formatBytes(item.ipfs.bytes_total)}</div>
                <div className="text-slate-500">{formatDate(item.ipfs.updated_at)}</div>
              </div>
            ) : (
              <p className="mt-2 text-xs text-slate-500">Нет информации.</p>
            )}
          </div>

          <div className="rounded-xl bg-slate-900/40 p-4 ring-1 ring-slate-800">
            <h4 className="text-xs font-semibold uppercase tracking-wide text-slate-500">Хранение</h4>
            {item.stored ? (
              <div className="mt-2 space-y-1 text-xs text-slate-300">
                <div>ID: {item.stored.stored_id ?? "—"}</div>
                <div>Владелец: {item.stored.owner_address ?? "—"}</div>
                <div>Тип: {item.stored.type ?? "—"}</div>
                {item.stored.user ? (
                  <div className="text-slate-400">Пользователь #{item.stored.user.id} · TG {item.stored.user.telegram_id}</div>
                ) : null}
                {item.stored.download_url ? (
                  <a
                    href={item.stored.download_url}
                    target="_blank"
                    rel="noreferrer"
                    className="inline-flex items-center gap-2 text-xs font-semibold text-sky-300 hover:text-sky-200"
                  >
                    Скачать оригинал
                  </a>
                ) : null}
              </div>
            ) : (
              <p className="mt-2 text-xs text-slate-500">В хранилище не найден.</p>
            )}
          </div>

          {derivativeDownloads.length > 0 ? (
            <div className="rounded-xl bg-slate-900/40 p-4 ring-1 ring-slate-800">
              <h4 className="text-xs font-semibold uppercase tracking-wide text-slate-500">Прямые ссылки</h4>
              <ul className="mt-2 space-y-1 text-xs text-slate-300">
                {derivativeDownloads.map((entry) => (
                  <li key={`${item.encrypted_cid}-link-${entry.kind}`}>
                    <a href={entry.url} target="_blank" rel="noreferrer" className="text-sky-300 hover:text-sky-200">
                      {entry.kind} · {entry.size_bytes ? formatBytes(entry.size_bytes) : "размер неизвестен"}
                    </a>
                  </li>
                ))}
              </ul>
            </div>
          ) : null}
        </div>
      </div>
    </div>
  );
};

export const AdminUploadsPage = () => {
  const { isAuthorized, handleRequestError } = useAdminContext();
  const [uploadsFilter, setUploadsFilter] = useState<UploadFilter>("all");
  const [uploadsSearch, setUploadsSearch] = useState("");

  const normalizedUploadsSearch = uploadsSearch.trim();
  const hasUploadsSearch = normalizedUploadsSearch.length > 0;
  const isUploadsFilterActive = uploadsFilter !== "all";
  const uploadsScanLimit = isUploadsFilterActive || hasUploadsSearch ? 200 : 100;

  const uploadsQuery = useAdminUploads(
    {
      filter: isUploadsFilterActive ? uploadsFilter : undefined,
      search: hasUploadsSearch ? normalizedUploadsSearch : undefined,
      limit: 40,
      scan: uploadsScanLimit,
    },
    {
      enabled: isAuthorized,
      keepPreviousData: true,
      refetchInterval: 30_000,
      onError: (error) => handleRequestError(error, "Не удалось загрузить список загрузок"),
    },
  );

  const uploadContents = uploadsQuery.data?.contents ?? [];
  const decoratedContents = useMemo(() => uploadContents.map((item) => classifyUpload(item)), [uploadContents]);

  if (uploadsQuery.isLoading && !uploadsQuery.data) {
    return (
      <Section id="uploads" title="Загрузки" description="Мониторинг загрузок и конвертации">
        Загрузка…
      </Section>
    );
  }

  if (!uploadsQuery.data) {
    return null;
  }

  const { states, total, recent, matching_total: matchingTotalRaw, category_totals: categoryTotalsRaw } = uploadsQuery.data;

  const categoryTotals = categoryTotalsRaw ?? {};
  const categoryCounts: Record<UploadCategory, number> = {
    issues: categoryTotals.issues ?? 0,
    processing: categoryTotals.processing ?? 0,
    ready: categoryTotals.ready ?? 0,
    unindexed: categoryTotals.unindexed ?? 0,
  };

  const searchNeedle = normalizedUploadsSearch.toLowerCase();
  const filteredEntries = decoratedContents.filter((entry) => {
    if (uploadsFilter !== "all" && !entry.categories.has(uploadsFilter)) {
      return false;
    }
    if (searchNeedle && !entry.searchText.includes(searchNeedle)) {
      return false;
    }
    return true;
  });

  const filteredContents = filteredEntries.map((entry) => entry.item);
  const totalMatches = typeof matchingTotalRaw === "number" ? matchingTotalRaw : filteredEntries.length;
  const filtersActive = isUploadsFilterActive || hasUploadsSearch;
  const noMatches = totalMatches === 0;
  const issueEntries = filteredEntries.filter((entry) => entry.categories.has("issues")).slice(0, 3);
  const filterOptions: Array<{ id: UploadFilter; label: string; count: number }> = [
    { id: "all", label: "Все", count: totalMatches },
    { id: "issues", label: "Ошибки", count: categoryCounts.issues },
    { id: "processing", label: "В обработке", count: categoryCounts.processing },
    { id: "ready", label: "Готово", count: categoryCounts.ready },
    { id: "unindexed", label: "Без on-chain", count: categoryCounts.unindexed },
  ];

  const handleResetFilters = () => {
    setUploadsFilter("all");
    setUploadsSearch("");
  };

  return (
    <Section
      id="uploads"
      title="Загрузки"
      description="Полная картина обработки контента: цепочка загрузки, конверсия, IPFS и публикация"
      actions={
        <button
          type="button"
          className="rounded-lg bg-slate-800 px-3 py-2 text-xs font-semibold text-slate-200 ring-1 ring-slate-700 transition hover:bg-slate-700"
          onClick={() => uploadsQuery.refetch()}
          disabled={uploadsQuery.isFetching}
        >
          {uploadsQuery.isFetching ? "Обновляем…" : "Обновить"}
        </button>
      }
    >
      <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
        {Object.entries(states ?? {}).map(([key, value]) => (
          <div key={key} className="rounded-xl bg-slate-950/60 p-4 ring-1 ring-slate-800">
            <p className="text-xs uppercase tracking-wide text-slate-500">{key}</p>
            <p className="mt-2 text-lg font-semibold text-slate-100">{numberFormatter.format(value)}</p>
          </div>
        ))}
        <div className="rounded-xl bg-slate-950/60 p-4 ring-1 ring-slate-800">
          <p className="text-xs uppercase tracking-wide text-slate-500">Всего</p>
          <p className="mt-2 text-lg font-semibold text-slate-100">{numberFormatter.format(total)}</p>
          <p className="mt-1 text-xs text-slate-500">Отфильтровано: {numberFormatter.format(filteredContents.length)}</p>
        </div>
        <div className="rounded-xl bg-slate-950/60 p-4 ring-1 ring-slate-800">
          <p className="text-xs uppercase tracking-wide text-slate-500">Активные задачи</p>
          <p className="mt-2 text-lg font-semibold text-slate-100">{numberCompact.format(categoryCounts.processing)}</p>
          <p className="mt-1 text-xs text-slate-500">{numberFormatter.format(categoryCounts.issues)} с ошибками</p>
        </div>
      </div>

      <div className="flex flex-col gap-3 md:flex-row md:items-center md:justify-between">
        <div className="flex flex-wrap items-center gap-2">
          {filterOptions.map((option) => {
            const isActive = option.id === uploadsFilter;
            return (
              <button
                key={`upload-filter-${option.id}`}
                type="button"
                onClick={() => setUploadsFilter(option.id)}
                className={clsx(
                  "inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-semibold transition",
                  isActive
                    ? "border-sky-500 bg-sky-500/20 text-sky-100 shadow-inner shadow-sky-500/20"
                    : "border-slate-700 text-slate-300 hover:border-slate-500 hover:text-slate-100",
                )}
              >
                {option.label}
                <span className="rounded-full bg-slate-900/60 px-2 py-0.5 text-[10px] font-semibold tracking-wide text-slate-300">
                  {numberFormatter.format(option.count)}
                </span>
              </button>
            );
          })}
          {filtersActive ? (
            <button
              type="button"
              onClick={handleResetFilters}
              className="inline-flex items-center rounded-full border border-slate-700 px-3 py-1 text-xs font-semibold text-slate-300 transition hover:border-slate-500 hover:text-slate-100"
            >
              Сбросить
            </button>
          ) : null}
        </div>
        <div className="relative w-full md:w-72">
          <input
            type="search"
            value={uploadsSearch}
            onChange={(event) => setUploadsSearch(event.target.value)}
            placeholder="Название, CID, пользователь…"
            className="w-full rounded-lg border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-slate-100 placeholder:text-slate-500 focus:border-sky-500 focus:outline-none focus:ring-1 focus:ring-sky-500"
          />
        </div>
      </div>

      {issueEntries.length > 0 ? (
        <div className="rounded-xl border border-amber-500/40 bg-amber-500/10 p-4 text-xs text-amber-100">
          <h3 className="text-sm font-semibold text-amber-50">Последние проблемы</h3>
          <ul className="mt-2 space-y-1">
            {issueEntries.map((entry) => (
              <li key={`issue-${entry.item.encrypted_cid}`} className="flex items-center justify-between gap-2">
                <span className="truncate text-sm text-amber-50">{entry.item.title || entry.item.encrypted_cid}</span>
                <span className="text-[11px] text-amber-200">{formatDate(entry.item.updated_at)}</span>
              </li>
            ))}
          </ul>
        </div>
      ) : null}

      {noMatches ? (
        <div className="rounded-xl border border-slate-800 bg-slate-950/40 p-6 text-sm text-slate-400">
          Под подходящие условия не найдено загрузок.
        </div>
      ) : (
        <div className="grid gap-5">
          {filteredEntries.map((entry) => (
            <UploadCard key={entry.item.encrypted_cid} decoration={entry} />
          ))}
        </div>
      )}

      <div className="rounded-xl bg-slate-950/40 p-4 ring-1 ring-slate-800">
        <h3 className="text-sm font-semibold uppercase tracking-wide text-slate-400">Последние события</h3>
        <div className="mt-2 overflow-x-auto">
          <table className="min-w-full divide-y divide-slate-800 text-left text-xs">
            <thead className="bg-slate-950/70 text-[11px] uppercase tracking-wide text-slate-400">
              <tr>
                <th className="px-3 py-2">ID</th>
                <th className="px-3 py-2">Файл</th>
                <th className="px-3 py-2">Размер</th>
                <th className="px-3 py-2">Состояние</th>
                <th className="px-3 py-2">CID</th>
                <th className="px-3 py-2">Ошибка</th>
                <th className="px-3 py-2">Обновлено</th>
              </tr>
            </thead>
            <tbody className="divide-y divide-slate-900/60">
              {recent.map((task) => (
                <tr key={task.id} className="hover:bg-slate-900/50">
                  <td className="px-3 py-2 font-mono text-[10px] text-slate-400">{task.id}</td>
                  <td className="px-3 py-2">{task.filename ?? "—"}</td>
                  <td className="px-3 py-2">{formatBytes(task.size_bytes)}</td>
                  <td className="px-3 py-2">
                    <Badge tone={task.state === "done" ? "success" : task.state === "failed" ? "danger" : "warn"}>
                      {task.state}
                    </Badge>
                  </td>
                  <td className="px-3 py-2 font-mono text-[10px] text-slate-500">{task.encrypted_cid ?? "—"}</td>
                  <td className="px-3 py-2 text-rose-200">{task.error ?? "—"}</td>
                  <td className="px-3 py-2 text-slate-400">{formatDate(task.updated_at)}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </Section>
  );
};
