import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useClient } from 'hooks/useClient';
import type { HeadscalePolicy } from './shared';
import { POLICY_READ_ONLY, POLICY_REJECTED } from './shared';

// The active server's ACL policy. One document, one query, one mutation — the interesting part is entirely
// in how a failed save is classified, because the two failures need opposite reactions from the owner:
// "your document is wrong, here is where" versus "this server does not accept written policies at all".

const POLICY_KEY = ['headscale', 'policy'] as const;
const PATH = '/offscale/_officer/policy';

/** What a rejected save means. `rejected` carries Headscale's own message; `readOnly` ends the editing. */
export type PolicySaveFailure = { kind: 'rejected' | 'readOnly' | 'unknown'; message: string };

/** useClient throws `{status, message}` with the raw body text — dig the sidecar's `{error, code}` out. */
export function policySaveFailure(err: unknown): PolicySaveFailure {
  const raw = (err as { message?: unknown } | null)?.message;
  if (typeof raw !== 'string' || !raw) return { kind: 'unknown', message: 'The policy could not be saved' };

  try {
    const parsed = JSON.parse(raw) as { error?: unknown; code?: unknown };
    const message = typeof parsed.error === 'string' && parsed.error ? parsed.error : 'The policy could not be saved';
    if (parsed.code === POLICY_READ_ONLY) return { kind: 'readOnly', message };
    if (parsed.code === POLICY_REJECTED) return { kind: 'rejected', message };
    return { kind: 'unknown', message };
  } catch {
    return { kind: 'unknown', message: raw.slice(0, 300) };
  }
}

/** What the assistant proposes. Never saved by the hook — PolicyView puts it in the draft. */
export type PolicyProposal = { explanation: string; policy: string };

/** The assistant's failures are all one sentence to the owner; only the sidecar's `error` field is useful. */
export function assistFailure(err: unknown): string {
  const raw = (err as { message?: unknown } | null)?.message;
  if (typeof raw !== 'string' || !raw) return 'The assistant could not be reached';
  try {
    const parsed = JSON.parse(raw) as { error?: unknown };
    return typeof parsed.error === 'string' && parsed.error ? parsed.error : 'The assistant could not be reached';
  } catch {
    return raw.slice(0, 300);
  }
}

/**
 * Ask for a revised policy in English. Separate from `useHeadscalePolicy` because it is a different kind of
 * thing: no cache, no query key, nothing to invalidate — one request, one answer, discarded on reload.
 */
export function useHeadscalePolicyAssist() {
  const { post } = useClient();
  return useMutation({
    mutationFn: (input: { prompt: string; policy: string }) => post<PolicyProposal>(`${PATH}/assist`, input),
  });
}

export function useHeadscalePolicy() {
  const { get, put } = useClient();
  const qc = useQueryClient();

  const query = useQuery({
    queryKey: POLICY_KEY,
    queryFn: () => get<HeadscalePolicy>(PATH),
    // No polling and a long staleTime: this is a document someone is editing. A background refetch that
    // replaced the textarea under a half-written rule would be the worst thing this screen could do.
    staleTime: 5 * 60_000,
    refetchOnWindowFocus: false,
  });

  const save = useMutation({
    mutationFn: (policy: string) => put<HeadscalePolicy>(PATH, { policy }),
    // Seed the cache from the response rather than invalidating: a refetch here would race the editor's
    // own state and could show the pre-save document for a frame.
    onSuccess: (data) => qc.setQueryData(POLICY_KEY, data),
  });

  return { policy: query.data ?? null, isLoading: query.isLoading, error: query.error, refetch: query.refetch, save };
}
