import { useEffect, useState } from 'react';
import { mediaApi } from './media-api';

// Per-URL HMAC signing for <img>/<video> src — replaces the legacy
// `?token=<JWT>` pattern (removed server-side 2026-04-12). The backend
// rejects ?token=, so admin previews of unpublished media 404 unless we
// hit POST /api/media/sign for a short-lived path-bound signature.

interface SignResponse {
  url: string;
  sig: string;
  exp: string;
  uid: string;
  expiresAt: string;
}

interface CacheEntry {
  url: string;
  exp: number;
}

const cache = new Map<string, CacheEntry>();
const inflight = new Map<string, Promise<string>>();

// Refresh a bit before expiry so an in-flight <video> stream doesn't
// hit a freshly-expired signature mid-playback.
const REFRESH_BUFFER_SECONDS = 30;

function clientToServerPath(clientPath: string): string {
  // Strip query string before translating prefix.
  const queryIdx = clientPath.indexOf('?');
  const path = queryIdx === -1 ? clientPath : clientPath.slice(0, queryIdx);
  const query = queryIdx === -1 ? '' : clientPath.slice(queryIdx);
  // Nginx rewrites /media/* → /api/* before the request reaches media-api.
  // The signing endpoint requires the post-rewrite (server-side) path.
  const serverPath = path.startsWith('/media/')
    ? '/api/' + path.slice('/media/'.length)
    : path;
  return serverPath + query;
}

export async function signedMediaUrl(clientPath: string): Promise<string> {
  if (!clientPath) return clientPath;

  const now = Math.floor(Date.now() / 1000);
  const cached = cache.get(clientPath);
  if (cached && cached.exp - REFRESH_BUFFER_SECONDS > now) {
    return cached.url;
  }

  const existing = inflight.get(clientPath);
  if (existing) return existing;

  const promise = (async () => {
    try {
      const serverPath = clientToServerPath(clientPath);
      const { data } = await mediaApi.post<SignResponse>('/media/sign', {
        path: serverPath,
      });
      const sep = clientPath.includes('?') ? '&' : '?';
      const query = `sig=${data.sig}&exp=${data.exp}&uid=${data.uid}`;
      const url = `${clientPath}${sep}${query}`;
      cache.set(clientPath, { url, exp: Number(data.exp) });
      return url;
    } finally {
      inflight.delete(clientPath);
    }
  })();

  inflight.set(clientPath, promise);
  return promise;
}

/**
 * React hook: returns a signed media URL for embedding in <img>/<video> src.
 *
 * Returns `undefined` until the first sign call completes. Pass `null` /
 * `undefined` / empty string to disable (returns `undefined`).
 *
 * Cached across re-renders, so a list of 50 thumbnails issues 50 sign
 * requests on initial mount and zero on subsequent renders within the TTL.
 */
export function useSignedMediaUrl(
  clientPath: string | null | undefined
): string | undefined {
  const [signed, setSigned] = useState<string | undefined>(() => {
    if (!clientPath) return undefined;
    const cached = cache.get(clientPath);
    if (!cached) return undefined;
    const now = Math.floor(Date.now() / 1000);
    return cached.exp - REFRESH_BUFFER_SECONDS > now ? cached.url : undefined;
  });

  useEffect(() => {
    if (!clientPath) {
      setSigned(undefined);
      return;
    }
    let cancelled = false;
    signedMediaUrl(clientPath)
      .then((url) => {
        if (!cancelled) setSigned(url);
      })
      .catch(() => {
        // Leave undefined; <img onError> / <video onError> can show a fallback.
      });
    return () => {
      cancelled = true;
    };
  }, [clientPath]);

  return signed;
}
