import { useNostr } from '@nostrify/react';
import { useQuery } from '@tanstack/react-query';
import type { NostrEvent, NostrFilter } from '@nostrify/nostrify';
import { ZAPSTORE_RELAY } from '@/lib/appRelays';

/** Kinds whose canonical home is the Zapstore relay. */
const ZAPSTORE_KINDS = [32267, 30063];

/**
 * Extract write relay URLs from a NIP-65 (kind 10002) relay list event.
 * Write relays are where the author publishes their content.
 * Tags with no marker are both read+write; tags with "write" are write-only.
 */
function extractWriteRelays(event: NostrEvent): string[] {
  const relays = new Set<string>();
  for (const [name, url, marker] of event.tags) {
    if (name !== 'r' || marker === 'read' || !url) continue;
    try {
      const parsed = new URL(url);
      if (parsed.protocol === 'wss:') {
        relays.add(parsed.href);
      }
    } catch {
      // skip malformed URLs
    }
  }
  return [...relays];
}

/**
 * Last-resort: fetch the author's NIP-65 relay list and query their write relays
 * for the target event. Returns the event if found, or null.
 */
async function queryAuthorRelays(
  nostr: { query: (filters: NostrFilter[], opts?: { signal?: AbortSignal }) => Promise<NostrEvent[]>; group: (urls: string[]) => { query: (filters: NostrFilter[], opts?: { signal?: AbortSignal }) => Promise<NostrEvent[]> } },
  authorPubkey: string,
  eventFilter: NostrFilter[],
  signal: AbortSignal,
): Promise<NostrEvent | null> {
  try {
    // Fetch the author's NIP-65 relay list from our connected relays
    const relayListSignal = AbortSignal.any([signal, AbortSignal.timeout(5000)]);
    const relayListEvents = await nostr.query(
      [{ kinds: [10002], authors: [authorPubkey], limit: 1 }],
      { signal: relayListSignal },
    );

    if (relayListEvents.length === 0) return null;

    const writeRelays = extractWriteRelays(relayListEvents[0]).slice(0, 5);
    if (writeRelays.length === 0) return null;

    // Query the author's write relays for the target event
    const authorRelaySignal = AbortSignal.any([signal, AbortSignal.timeout(6000)]);
    const events = await nostr.group(writeRelays).query(eventFilter, { signal: authorRelaySignal });
    return events.length > 0 ? events[0] : null;
  } catch {
    return null;
  }
}

/** Fetches a single Nostr event by its hex ID, optionally querying relay hints. */
export function useEvent(eventId: string | undefined, relays?: string[], authorHint?: string) {
  const { nostr } = useNostr();

  return useQuery<NostrEvent | null>({
    queryKey: ['event', eventId ?? ''],
    queryFn: async () => {
      if (!eventId) return null;
      const filter: NostrFilter[] = [{ ids: [eventId], limit: 1 }];

      // 1. Query the user's configured relays first (batched automatically)
      const events = await nostr.query(filter, { signal: AbortSignal.timeout(5000) });
      if (events.length > 0) return events[0];

      // 2. If not found and we have relay hints, try those relays directly
      if (relays && relays.length > 0) {
        try {
          const hintEvents = await nostr.group(relays).query(filter, { signal: AbortSignal.timeout(5000) });
          if (hintEvents.length > 0) return hintEvents[0];
        } catch {
          // relay hint query failed — fall through
        }
      }

      // 3. Last resort: if we have the author's pubkey, fetch their NIP-65 relay
      //    list and try their write relays (where they publish content)
      if (authorHint) {
        const found = await queryAuthorRelays(nostr, authorHint, filter, AbortSignal.timeout(10000));
        if (found) return found;
      }

      return null;
    },
    enabled: !!eventId,
    staleTime: 5 * 60 * 1000,
  });
}

/** Coordinates for an addressable event (naddr). */
export interface AddrCoords {
  kind: number;
  pubkey: string;
  identifier: string;
}

/** Fetches a single addressable Nostr event by kind + pubkey + d-tag, optionally querying relay hints. */
export function useAddrEvent(addr: AddrCoords | undefined, relays?: string[]) {
  const { nostr } = useNostr();

  return useQuery<NostrEvent | null>({
    queryKey: ['addr-event', addr?.kind ?? 0, addr?.pubkey ?? '', addr?.identifier ?? ''],
    queryFn: async () => {
      if (!addr) return null;
      const filter: NostrFilter[] = [{ kinds: [addr.kind], authors: [addr.pubkey], '#d': [addr.identifier], limit: 1 }];

      // For Zapstore kinds, try the canonical relay first for fastest results
      if (ZAPSTORE_KINDS.includes(addr.kind)) {
        try {
          const zapEvents = await nostr.relay(ZAPSTORE_RELAY).query(filter, { signal: AbortSignal.timeout(5000) });
          if (zapEvents.length > 0) return zapEvents[0];
        } catch {
          // zapstore relay failed — fall through to normal flow
        }
      }

      // 1. Query the user's configured relays
      const events = await nostr.query(filter, { signal: AbortSignal.timeout(5000) });
      if (events.length > 0) return events[0];

      // 2. If not found and we have relay hints, try those relays directly
      if (relays && relays.length > 0) {
        try {
          const hintEvents = await nostr.group(relays).query(filter, { signal: AbortSignal.timeout(5000) });
          if (hintEvents.length > 0) return hintEvents[0];
        } catch {
          // relay hint query failed — fall through
        }
      }

      // 3. Last resort: fetch the author's NIP-65 relay list and try their
      //    write relays (naddr always includes the author pubkey)
      const found = await queryAuthorRelays(nostr, addr.pubkey, filter, AbortSignal.timeout(10000));
      if (found) return found;

      return null;
    },
    enabled: !!addr,
    staleTime: 5 * 60 * 1000,
  });
}
