// Admin: LLM queue configuration — provider chains + system prompts per queue.

import { useState } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { ChevronUp, ChevronDown, Trash2 } from 'lucide-react';
import { HydratedIsland } from '@/components/HydratedIsland';
import { getCsrfToken } from '@/lib/csrf';
import { useT } from '@/lib/i18n/react';
import { interpolate } from '@/lib/i18n/interpolate';
import { Button } from '@/components/ui/primitives/Button';
import { IconButton } from '@/components/ui/primitives/IconButton';
import { Combobox } from '@/components/ui/inputs/Combobox';
import { Label } from '@/components/ui/primitives/Label';
import { Textarea } from '@/components/ui/primitives/Textarea';
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from '@/components/ui/primitives/Select';
import { InlineNotice } from '@/components/ui/feedback/InlineNotice';
import { Spinner } from '@/components/ui/feedback/Spinner';
import { ErrorBoundary } from '@/components/ui/feedback/ErrorBoundary';
import { Stack } from '@/components/ui/layout/Stack';
import { Row } from '@/components/ui/layout/Row';
import { useProviderModels } from '@/features/admin-support/internals/useAdminSupport';

// ─── Types ────────────────────────────────────────────────────────────────────

type QueueId = 'moderation' | 'translation' | 'support';

interface EnrichedChainEntry {
  llmProviderId: string;
  providerName: string;
  providerType: string;
  model: string;
}

interface EnrichedLlmQueue {
  id: QueueId;
  name: string;
  chain: EnrichedChainEntry[];
  prompt: string;
  updatedAt: string;
}

interface ChainEntryDraft {
  llmProviderId: string;
  model: string;
}

interface LlmProviderOption {
  id: string;
  name: string;
  type: string;
}

const QUEUE_IDS: QueueId[] = ['moderation', 'translation', 'support'];

const QUEUE_TITLE_KEYS: Record<
  QueueId,
  'queue_moderation' | 'queue_translation' | 'queue_support'
> = {
  moderation: 'queue_moderation',
  translation: 'queue_translation',
  support: 'queue_support',
};

const adminLlmQueuesKeys = {
  all: ['admin', 'llm-queues'] as const,
  providers: ['admin', 'support', 'providers'] as const,
};

// ─── Helpers ──────────────────────────────────────────────────────────────────

async function jsonOrThrow(res: Response) {
  const data = (await res.json()) as Record<string, unknown>;
  if (!res.ok || !data.ok) {
    throw new Error((data.error as string) ?? `HTTP ${res.status}`);
  }
  return data;
}

/** defineApi respondOk flat-merges handler data: `{ ok: true, ...fields }`. */
function readApiField<T>(payload: Record<string, unknown>, key: string): T {
  if (payload[key] !== undefined) return payload[key] as T;
  const nested = payload.data as Record<string, unknown> | undefined;
  if (nested?.[key] !== undefined) return nested[key] as T;
  throw new Error(`Missing "${key}" in API response`);
}

function toChainDraft(chain: EnrichedChainEntry[]): ChainEntryDraft[] {
  return chain.map(({ llmProviderId, model }) => ({ llmProviderId, model }));
}

function chainsEqual(a: ChainEntryDraft[], b: ChainEntryDraft[]): boolean {
  if (a.length !== b.length) return false;
  return a.every((entry, i) => {
    const other = b[i];
    if (!other) return false;
    return entry.llmProviderId === other.llmProviderId && entry.model === other.model;
  });
}

function moveChainEntry(
  chain: ChainEntryDraft[],
  index: number,
  direction: -1 | 1,
): ChainEntryDraft[] {
  const target = index + direction;
  if (target < 0 || target >= chain.length) return chain;
  const next = [...chain];
  const current = next[index];
  const swap = next[target];
  if (!current || !swap) return chain;
  next[index] = swap;
  next[target] = current;
  return next;
}

// ─── Chain entry row ──────────────────────────────────────────────────────────

interface ChainEntryRowProps {
  queue: EnrichedLlmQueue;
  index: number;
  entry: ChainEntryDraft;
  chain: ChainEntryDraft[];
  updateChain: (next: ChainEntryDraft[]) => void;
  providers: LlmProviderOption[];
  providersLoading: boolean;
  t: ReturnType<typeof useT<'admin_llm_queues'>>;
}

function ChainEntryRow({
  queue,
  index,
  entry,
  chain,
  updateChain,
  providers,
  providersLoading,
  t,
}: ChainEntryRowProps) {
  const modelsQuery = useProviderModels(entry.llmProviderId || undefined);

  return (
    <div className="flex flex-col gap-1">
      <Row gap="3" align="end" wrap>
        <div className="min-w-0 flex-1">
          <Label
            htmlFor={`${queue.id}-provider-${index}`}
            className="text-text-muted mb-1 block text-xs"
          >
            {t('chain_entry_provider')}
          </Label>
          <Select
            value={entry.llmProviderId || undefined}
            onValueChange={(value) => {
              const next = [...chain];
              const current = next[index];
              if (!current) return;
              next[index] = { llmProviderId: value, model: current.model };
              updateChain(next);
            }}
          >
            <SelectTrigger
              id={`${queue.id}-provider-${index}`}
              disabled={providersLoading}
              className="w-full"
            >
              {providersLoading ? (
                <span className="text-text-muted flex items-center gap-2">
                  <Spinner variant="dots" size="sm" />
                  {t('provider_select_placeholder')}
                </span>
              ) : (
                <SelectValue placeholder={t('provider_select_placeholder')} />
              )}
            </SelectTrigger>
            <SelectContent>
              {providers.map((provider) => (
                <SelectItem key={provider.id} value={provider.id}>
                  {provider.name} ({provider.type})
                </SelectItem>
              ))}
              {entry.llmProviderId && !providers.some((p) => p.id === entry.llmProviderId) && (
                <SelectItem value={entry.llmProviderId}>
                  {interpolate(t('provider_orphan'), { id: entry.llmProviderId })}
                </SelectItem>
              )}
            </SelectContent>
          </Select>
        </div>

        <div className="min-w-0 flex-1">
          <Label
            htmlFor={`${queue.id}-model-${index}`}
            className="text-text-muted mb-1 block text-xs"
          >
            {t('chain_entry_model')}
          </Label>
          <Combobox
            id={`${queue.id}-model-${index}`}
            dir="ltr"
            value={entry.model || undefined}
            onValueChange={(model) => {
              const next = [...chain];
              const current = next[index];
              if (!current) return;
              next[index] = { llmProviderId: current.llmProviderId, model };
              updateChain(next);
            }}
            options={(modelsQuery.data?.models ?? []).map((m) => ({
              value: m.id,
              label: m.label,
            }))}
            loading={modelsQuery.isLoading && !!entry.llmProviderId}
            disabled={!entry.llmProviderId}
            placeholder={t('model_select_placeholder')}
            searchPlaceholder={t('model_search_placeholder')}
            emptyText={modelsQuery.data?.error ?? t('model_empty_hint')}
            pinnedValueHint={t('model_not_in_list')}
          />
        </div>

        <Row gap="1" align="center" className="shrink-0 pb-0.5">
          <IconButton
            variant="ghost"
            size="sm"
            shape="square"
            aria-label={t('chain_entry_move_up')}
            disabled={index === 0}
            onClick={() => updateChain(moveChainEntry(chain, index, -1))}
          >
            <ChevronUp className="size-4" aria-hidden />
          </IconButton>
          <IconButton
            variant="ghost"
            size="sm"
            shape="square"
            aria-label={t('chain_entry_move_down')}
            disabled={index === chain.length - 1}
            onClick={() => updateChain(moveChainEntry(chain, index, 1))}
          >
            <ChevronDown className="size-4" aria-hidden />
          </IconButton>
          <IconButton
            variant="ghost"
            size="sm"
            shape="square"
            className="text-danger-600 border-border-default ms-2 border-s ps-2"
            aria-label={t('chain_entry_remove')}
            onClick={() => updateChain(chain.filter((_, i) => i !== index))}
          >
            <Trash2 className="size-4" aria-hidden />
          </IconButton>
        </Row>
      </Row>
      <p className="text-text-muted text-xs">{t('model_helper_text')}</p>
    </div>
  );
}

// ─── Queue section ────────────────────────────────────────────────────────────

interface QueueSectionProps {
  queue: EnrichedLlmQueue;
  providers: LlmProviderOption[];
  providersLoading: boolean;
}

function QueueSection({ queue, providers, providersLoading }: QueueSectionProps) {
  const t = useT('admin_llm_queues');
  const queryClient = useQueryClient();

  const serverChain = toChainDraft(queue.chain);
  const [promptOverride, setPromptOverride] = useState<string | null>(null);
  const [chainOverride, setChainOverride] = useState<ChainEntryDraft[] | null>(null);
  const [saveError, setSaveError] = useState<string | null>(null);
  const [savedNotice, setSavedNotice] = useState(false);

  const prompt = promptOverride ?? queue.prompt;
  const chain = chainOverride ?? serverChain;

  const isDirty =
    (promptOverride !== null && promptOverride !== queue.prompt) ||
    (chainOverride !== null && !chainsEqual(chainOverride, serverChain));

  const saveMutation = useMutation({
    mutationFn: async (payload: { chain: ChainEntryDraft[]; prompt: string }) => {
      const res = await fetch(`/api/admin/llm-queues/${queue.id}`, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
          'x-csrf-token': getCsrfToken(),
        },
        body: JSON.stringify(payload),
      });
      return jsonOrThrow(res);
    },
    onSuccess: () => {
      void queryClient.invalidateQueries({ queryKey: adminLlmQueuesKeys.all });
      setPromptOverride(null);
      setChainOverride(null);
      setSavedNotice(true);
      setTimeout(() => setSavedNotice(false), 2000);
    },
  });

  function updateChain(next: ChainEntryDraft[]) {
    setChainOverride(next);
    setSaveError(null);
    setSavedNotice(false);
  }

  function updatePrompt(value: string) {
    setPromptOverride(value);
    setSaveError(null);
    setSavedNotice(false);
  }

  async function handleSave() {
    setSaveError(null);

    for (const entry of chain) {
      if (!entry.llmProviderId) {
        setSaveError(t('provider_required'));
        return;
      }
      if (!entry.model.trim()) {
        setSaveError(t('model_required'));
        return;
      }
    }

    try {
      await saveMutation.mutateAsync({
        chain: chain.map((entry) => ({
          llmProviderId: entry.llmProviderId,
          model: entry.model.trim(),
        })),
        prompt,
      });
    } catch (err) {
      setSaveError(err instanceof Error ? err.message : t('save_error'));
    }
  }

  const titleKey = QUEUE_TITLE_KEYS[queue.id];

  return (
    <section className="bg-surface-default rounded-2xl p-6 shadow-md">
      <Stack gap="5">
        <div>
          <h2 className="text-text-primary text-lg font-semibold">{t(titleKey)}</h2>
        </div>

        {isDirty && <InlineNotice tone="info" title={t('unsaved_changes')} />}

        <div>
          {queue.id !== 'support' ? (
            <>
              <Label
                htmlFor={`${queue.id}-prompt`}
                className="text-text-primary mb-2 block text-sm font-medium"
              >
                {t('section_prompt')}
              </Label>
              <Textarea
                id={`${queue.id}-prompt`}
                dir="ltr"
                rows={8}
                value={prompt}
                onChange={(e) => updatePrompt(e.target.value)}
                placeholder={t('section_prompt_placeholder')}
                className="font-mono text-xs"
              />
            </>
          ) : (
            <>
              <InlineNotice tone="info" title={t('support_prompt_notice')} className="mb-3" />
              <Button variant="secondary" size="sm" asChild>
                <a href="/admin/support/definitions">{t('support_goto_definitions')}</a>
              </Button>
            </>
          )}
        </div>

        <div>
          <Label className="text-text-primary mb-2 block text-sm font-medium">
            {t('section_chain')}
          </Label>

          {chain.length === 0 && (
            <InlineNotice tone="warning" title={t('chain_empty_warning')} className="mb-3" />
          )}

          <Stack gap="3">
            {chain.map((entry, index) => (
              <ChainEntryRow
                key={`${queue.id}-chain-${index}`}
                queue={queue}
                index={index}
                entry={entry}
                chain={chain}
                updateChain={updateChain}
                providers={providers}
                providersLoading={providersLoading}
                t={t}
              />
            ))}
          </Stack>

          <Button
            type="button"
            variant="ghost"
            size="sm"
            className="mt-3"
            onClick={() => updateChain([...chain, { llmProviderId: '', model: '' }])}
          >
            {t('chain_add')}
          </Button>
        </div>

        {saveError && <InlineNotice tone="danger" title={saveError} />}
        {savedNotice && <InlineNotice tone="success" title={t('saved')} />}

        <Row justify="end">
          <Button
            type="button"
            variant="primary"
            size="sm"
            disabled={!isDirty || saveMutation.isPending}
            onClick={handleSave}
          >
            {saveMutation.isPending ? (
              <Row gap="2" align="center">
                <Spinner variant="dots" size="sm" />
                {t('saving')}
              </Row>
            ) : (
              t('save')
            )}
          </Button>
        </Row>
      </Stack>
    </section>
  );
}

// ─── Main component ─────────────────────────────────────────────────────────────

function AdminLlmQueuesInner() {
  const t = useT('admin_llm_queues');

  const {
    data: queuesData,
    isLoading: queuesLoading,
    error: queuesError,
  } = useQuery({
    queryKey: adminLlmQueuesKeys.all,
    queryFn: async () => {
      const data = await fetch('/api/admin/llm-queues').then(jsonOrThrow);
      return readApiField<EnrichedLlmQueue[]>(data, 'queues');
    },
  });

  const { data: providersData, isLoading: providersLoading } = useQuery({
    queryKey: adminLlmQueuesKeys.providers,
    queryFn: async () => {
      const data = await fetch('/api/admin/support/providers').then(jsonOrThrow);
      return readApiField<LlmProviderOption[]>(data, 'providers');
    },
  });

  if (queuesLoading) {
    return (
      <div className="flex justify-center py-10">
        <Spinner variant="dots" size="lg" />
      </div>
    );
  }

  if (queuesError) {
    return (
      <InlineNotice
        tone="danger"
        title={queuesError instanceof Error ? queuesError.message : String(queuesError)}
      />
    );
  }

  const queuesById = new Map((queuesData ?? []).map((queue) => [queue.id, queue]));
  const providers = providersData ?? [];

  return (
    <ErrorBoundary>
      <Stack gap="6">
        <div>
          <p className="text-text-secondary text-sm">{t('page_subtitle')}</p>
        </div>
        {QUEUE_IDS.map((queueId) => {
          const queue = queuesById.get(queueId);
          if (!queue) return null;
          return (
            <QueueSection
              key={queueId}
              queue={queue}
              providers={providers}
              providersLoading={providersLoading}
            />
          );
        })}
      </Stack>
    </ErrorBoundary>
  );
}

export function AdminLlmQueues() {
  return (
    <HydratedIsland>
      <AdminLlmQueuesInner />
    </HydratedIsland>
  );
}
