import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
import { UserPlus, Upload, Loader2, X, Search, Crown, Users } from 'lucide-react';
import { useNostr } from '@nostrify/react';
import { useQueryClient } from '@tanstack/react-query';
import type { NostrEvent } from '@nostrify/nostrify';

import {
  Dialog,
  DialogContent,
  DialogHeader,
  DialogTitle,
  DialogDescription,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
import { getAvatarShape } from '@/lib/avatarShape';
import { EmojifiedText } from '@/components/CustomEmoji';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useNostrPublish } from '@/hooks/useNostrPublish';
import { useUploadFile } from '@/hooks/useUploadFile';
import { useToast } from '@/hooks/useToast';
import { useSearchProfiles, type SearchProfile } from '@/hooks/useSearchProfiles';
import { PortalContainerProvider } from '@/hooks/usePortalContainer';
import { genUserName } from '@/lib/genUserName';
import { fetchFreshEvent } from '@/lib/fetchFreshEvent';
import {
  COMMUNITY_DEFINITION_KIND,
  BADGE_DEFINITION_KIND,
  BADGE_AWARD_KIND,
  EMPTY_MODERATION,
  type CommunityMember,
  type CommunityMembership,
  type CommunityModeration,
  type ParsedCommunity,
} from '@/lib/communityUtils';
import { cn } from '@/lib/utils';

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

type MemberRole = 'moderator' | 'member';

interface PendingMember {
  profile: SearchProfile;
  role: MemberRole;
}

interface CommunityMembersCacheValue {
  membership: CommunityMembership;
  moderation: CommunityModeration;
  rankMap: Map<string, CommunityMember>;
}

interface AddMemberDialogProps {
  open: boolean;
  onOpenChange: (open: boolean) => void;
  /** The raw community definition event. */
  communityEvent: NostrEvent;
  /** Parsed community data. */
  community: ParsedCommunity;
  /** Whether the current user is the founder (can add moderators). */
  isFounder: boolean;
}

// ── Component ─────────────────────────────────────────────────────────────────

export function AddMemberDialog({
  open,
  onOpenChange,
  communityEvent,
  community,
  isFounder,
}: AddMemberDialogProps) {
  const { user } = useCurrentUser();
  const { nostr } = useNostr();
  const { toast } = useToast();
  const queryClient = useQueryClient();

  // Form state
  const [pendingMembers, setPendingMembers] = useState<PendingMember[]>([]);
  const [badgeImageUrl, setBadgeImageUrl] = useState('');
  const [badgeImagePreview, setBadgeImagePreview] = useState('');
  const [isPublishing, setIsPublishing] = useState(false);
  const [portalContainer, setPortalContainer] = useState<HTMLElement | undefined>(undefined);
  const fileInputRef = useRef<HTMLInputElement>(null);

  const dialogContentRef = useCallback((node: HTMLElement | null) => {
    setPortalContainer(node ?? undefined);
  }, []);

  // Mutations
  const { mutateAsync: publishEvent } = useNostrPublish();
  const { mutateAsync: uploadFile, isPending: isUploading } = useUploadFile();

  // Does this community already have a badge definition?
  const existingBadgeATag = community.ranks.find((r) => r.rank === 1)?.badgeATag;
  const hasBadge = !!existingBadgeATag;

  // Are there any pending members with the "member" role?
  const hasPendingMembers = pendingMembers.some((m) => m.role === 'member');
  // Will we need to create a badge? (members added + no badge exists yet)
  const needsBadgeCreation = hasPendingMembers && !hasBadge;

  const resetForm = useCallback(() => {
    setPendingMembers([]);
    setBadgeImageUrl('');
    setBadgeImagePreview('');
    setIsPublishing(false);
  }, []);

  const handleOpenChange = useCallback((nextOpen: boolean) => {
    if (!nextOpen) resetForm();
    onOpenChange(nextOpen);
  }, [onOpenChange, resetForm]);

  // ── People management ─────────────────────────────────────────────────────

  const addPerson = useCallback((profile: SearchProfile) => {
    if (!user) return;
    if (profile.pubkey === community.founderPubkey) {
      toast({ title: 'Already the founder' });
      return;
    }
    if (pendingMembers.some((m) => m.profile.pubkey === profile.pubkey)) {
      toast({ title: 'Already added' });
      return;
    }
    // Default role: member if they're not already a moderator, moderator if founder is adding
    const defaultRole: MemberRole = isFounder ? 'moderator' : 'member';
    setPendingMembers((prev) => [...prev, { profile, role: defaultRole }]);
  }, [user, community.founderPubkey, pendingMembers, isFounder, toast]);

  const removePerson = useCallback((pubkey: string) => {
    setPendingMembers((prev) => prev.filter((m) => m.profile.pubkey !== pubkey));
  }, []);

  const toggleRole = useCallback((pubkey: string) => {
    if (!isFounder) return; // Only founder can toggle to moderator
    setPendingMembers((prev) => prev.map((m) =>
      m.profile.pubkey === pubkey
        ? { ...m, role: m.role === 'moderator' ? 'member' : 'moderator' }
        : m,
    ));
  }, [isFounder]);

  const applyOptimisticMembership = useCallback((members: PendingMember[], awardEvents: Map<string, NostrEvent>) => {
    queryClient.setQueryData<CommunityMembersCacheValue>(['community-members', community.aTag], (prev) => {
      const moderation = prev?.moderation ?? EMPTY_MODERATION;
      const rankMap = new Map(prev?.rankMap ?? []);
      const membershipByPubkey = new Map(
        (prev?.membership.members ?? []).map((member) => [member.pubkey, member] as const),
      );

      const seedRankZero = (pubkey: string) => {
        if (moderation.bannedPubkeys.has(pubkey)) return;
        const member: CommunityMember = { pubkey, rank: 0 };
        if (!membershipByPubkey.has(pubkey)) membershipByPubkey.set(pubkey, member);
        if (!rankMap.has(pubkey)) rankMap.set(pubkey, member);
      };

      seedRankZero(community.founderPubkey);
      community.moderatorPubkeys.forEach(seedRankZero);

      for (const pending of members) {
        if (moderation.bannedPubkeys.has(pending.profile.pubkey)) continue;

        const nextMember: CommunityMember = pending.role === 'moderator'
          ? { pubkey: pending.profile.pubkey, rank: 0 }
          : {
              pubkey: pending.profile.pubkey,
              rank: 1,
              awardEvent: awardEvents.get(pending.profile.pubkey),
              awardedBy: user?.pubkey,
            };

        const current = membershipByPubkey.get(nextMember.pubkey);
        if (!current || nextMember.rank < current.rank) {
          membershipByPubkey.set(nextMember.pubkey, nextMember);
        }

        const currentRank = rankMap.get(nextMember.pubkey);
        if (!currentRank || nextMember.rank < currentRank.rank) {
          rankMap.set(nextMember.pubkey, nextMember);
        }
      }

      const membership: CommunityMembership = {
        members: Array.from(membershipByPubkey.values()).sort((a, b) => a.rank - b.rank),
      };

      return { membership, moderation, rankMap };
    });
  }, [community.aTag, community.founderPubkey, community.moderatorPubkeys, queryClient, user?.pubkey]);

  // ── Badge image upload ────────────────────────────────────────────────────

  const handleBadgeFileSelect = useCallback(async (file: File) => {
    if (!file.type.startsWith('image/')) {
      toast({ title: 'Invalid file', description: 'Please select an image file.', variant: 'destructive' });
      return;
    }
    const reader = new FileReader();
    reader.onload = (e) => setBadgeImagePreview(e.target?.result as string);
    reader.readAsDataURL(file);
    try {
      const [[, url]] = await uploadFile(file);
      setBadgeImageUrl(url);
      toast({ title: 'Badge image uploaded' });
    } catch {
      setBadgeImagePreview('');
      toast({ title: 'Upload failed', description: 'Please try again.', variant: 'destructive' });
    }
  }, [uploadFile, toast]);

  const handleBadgeDrop = useCallback((e: React.DragEvent) => {
    e.preventDefault();
    const file = e.dataTransfer.files[0];
    if (file) handleBadgeFileSelect(file);
  }, [handleBadgeFileSelect]);

  // ── Publish ───────────────────────────────────────────────────────────────

  const handleSubmit = useCallback(async () => {
    if (!user || pendingMembers.length === 0) return;

    setIsPublishing(true);
    try {
      const newModerators = pendingMembers.filter((m) => m.role === 'moderator');
      const newMembers = pendingMembers.filter((m) => m.role === 'member');

      let badgeATag = existingBadgeATag;

      // Step 1: Create badge definition if needed
      if (newMembers.length > 0 && !hasBadge) {
        const badgeDTag = `${community.dTag}-member`;
        const badgeTags: string[][] = [
          ['d', badgeDTag],
          ['name', 'Member'],
          ['description', `Member of ${community.name}`],
        ];
        if (badgeImageUrl) {
          badgeTags.push(['image', badgeImageUrl, '1024x1024']);
        }
        badgeTags.push(['alt', `Badge definition: Member of ${community.name}`]);

        const badgeEvent = await publishEvent({
          kind: BADGE_DEFINITION_KIND,
          content: '',
          tags: badgeTags,
        } as Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>);

        badgeATag = `${BADGE_DEFINITION_KIND}:${badgeEvent.pubkey}:${badgeDTag}`;
      }

      // Step 2: Republish community definition if needed
      // Needed when: adding moderators (new p tags) OR badge was just created (new a tag)
      const needsCommunityUpdate = newModerators.length > 0 || (newMembers.length > 0 && !hasBadge);

      if (needsCommunityUpdate) {
        // Fetch fresh community event to avoid stale overwrites
        const prev = await fetchFreshEvent(nostr, {
          kinds: [COMMUNITY_DEFINITION_KIND],
          authors: [communityEvent.pubkey],
          '#d': [community.dTag],
        });

        const baseTags = prev?.tags ?? communityEvent.tags;
        const updatedTags = [...baseTags];

        // Add new moderator p tags
        for (const mod of newModerators) {
          // Don't add if already exists
          const exists = updatedTags.some(
            ([n, v, , role]) => n === 'p' && v === mod.profile.pubkey && role === 'moderator',
          );
          if (!exists) {
            updatedTags.push(['p', mod.profile.pubkey, '', 'moderator']);
          }
        }

        // Add badge a tag if badge was just created
        if (badgeATag && !hasBadge) {
          updatedTags.push(['a', badgeATag, '', '1']);
        }

        await publishEvent({
          kind: COMMUNITY_DEFINITION_KIND,
          content: prev?.content ?? '',
          tags: updatedTags,
          prev: prev ?? undefined,
        } as Omit<NostrEvent, 'id' | 'pubkey' | 'sig'> & { prev?: NostrEvent });
      }

      // Step 3: Publish badge awards for each member
      const memberAwardEvents = new Map<string, NostrEvent>();
      if (newMembers.length > 0 && badgeATag) {
        for (const member of newMembers) {
          const awardEvent = await publishEvent({
            kind: BADGE_AWARD_KIND,
            content: '',
            tags: [
              ['a', badgeATag],
              ['p', member.profile.pubkey],
              ['alt', `Badge award: Member in ${community.name}`],
            ],
          } as Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>);
          memberAwardEvents.set(member.profile.pubkey, awardEvent);
        }
      }

      applyOptimisticMembership(pendingMembers, memberAwardEvents);
      queryClient.invalidateQueries({ queryKey: ['my-communities'], exact: false });
      if (!hasBadge && newMembers.length > 0) {
        queryClient.invalidateQueries({ queryKey: ['badge-feed'] });
      }

      const addedCount = pendingMembers.length;
      toast({ title: `Added ${addedCount} ${addedCount === 1 ? 'person' : 'people'} to the community` });
      handleOpenChange(false);
    } catch (err) {
      toast({
        title: 'Failed to add members',
        description: err instanceof Error ? err.message : 'Please try again.',
        variant: 'destructive',
      });
    } finally {
      setIsPublishing(false);
    }
  }, [
    user, pendingMembers, existingBadgeATag, hasBadge, community, communityEvent,
    badgeImageUrl, nostr, publishEvent, queryClient, toast, handleOpenChange, applyOptimisticMembership,
  ]);

  if (!user) return null;

  return (
    <Dialog open={open} onOpenChange={handleOpenChange}>
      <DialogContent ref={dialogContentRef} className="sm:max-w-md gap-0 p-0 overflow-visible">
        <PortalContainerProvider value={portalContainer}>
        <DialogHeader className="px-5 pt-5 pb-3">
          <DialogTitle className="flex items-center gap-2">
            <UserPlus className="size-5 text-primary" />
            Add Members
          </DialogTitle>
          <DialogDescription>
            {isFounder
              ? 'Add moderators or members to your community.'
              : 'Invite members to the community.'}
          </DialogDescription>
        </DialogHeader>

        <ScrollArea className="max-h-[60vh]">
          <div className="px-5 pb-5 space-y-4">
            {/* People search */}
            <div className="space-y-1.5">
              <Label>Search people</Label>
              <PersonSearch
                onAdd={addPerson}
                excludePubkeys={[
                  community.founderPubkey,
                  ...pendingMembers.map((m) => m.profile.pubkey),
                ]}
              />
            </div>

            {/* Pending members list */}
            {pendingMembers.length > 0 && (
              <div className="space-y-1.5">
                <Label>
                  People to add
                  <span className="text-muted-foreground font-normal ml-1">({pendingMembers.length})</span>
                </Label>
                <div className="space-y-1">
                  {pendingMembers.map((pm) => (
                    <PendingMemberChip
                      key={pm.profile.pubkey}
                      pending={pm}
                      onRemove={removePerson}
                      onToggleRole={isFounder ? toggleRole : undefined}
                    />
                  ))}
                </div>
              </div>
            )}

            {/* Badge image — only shown when badge needs to be created */}
            {needsBadgeCreation && (
              <div className="space-y-1.5">
                <Label>
                  Member Badge Image
                  <span className="text-muted-foreground font-normal ml-1">(optional)</span>
                </Label>
                <div
                  role="button"
                  tabIndex={0}
                  onClick={() => fileInputRef.current?.click()}
                  onDrop={handleBadgeDrop}
                  onDragOver={(e) => e.preventDefault()}
                  onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') fileInputRef.current?.click(); }}
                  className="relative flex flex-col items-center justify-center w-full h-24 border-2 border-dashed border-border rounded-xl bg-secondary/5 hover:bg-secondary/10 transition-colors cursor-pointer overflow-hidden"
                >
                  {badgeImagePreview ? (
                    <img src={badgeImagePreview} alt="Badge preview" className="h-full object-contain" />
                  ) : isUploading ? (
                    <div className="flex flex-col items-center gap-1.5 text-muted-foreground">
                      <Loader2 className="size-5 animate-spin" />
                      <span className="text-xs">Uploading...</span>
                    </div>
                  ) : (
                    <div className="flex flex-col items-center gap-1.5 text-muted-foreground">
                      <Upload className="size-5 opacity-40" />
                      <span className="text-xs">Drop an image or click to upload</span>
                    </div>
                  )}
                  <input
                    ref={fileInputRef}
                    type="file"
                    accept="image/*"
                    className="hidden"
                    onChange={(e) => {
                      const file = e.target.files?.[0];
                      if (file) handleBadgeFileSelect(file);
                    }}
                  />
                </div>
              </div>
            )}

            {/* Submit button */}
            <Button
              onClick={handleSubmit}
              disabled={pendingMembers.length === 0 || isPublishing || isUploading}
              className="w-full gap-2"
            >
              {isPublishing ? (
                <><Loader2 className="size-4 animate-spin" /> Adding...</>
              ) : (
                <><UserPlus className="size-4" /> Add {pendingMembers.length || ''} {pendingMembers.length === 1 ? 'Person' : pendingMembers.length > 1 ? 'People' : 'Members'}</>
              )}
            </Button>
          </div>
        </ScrollArea>
        </PortalContainerProvider>
      </DialogContent>
    </Dialog>
  );
}

// ── Sub-Components ────────────────────────────────────────────────────────────

/** Inline type-ahead person search. */
function PersonSearch({
  onAdd,
  excludePubkeys,
}: {
  onAdd: (profile: SearchProfile) => void;
  excludePubkeys: string[];
}) {
  const [query, setQuery] = useState('');
  const [dropdownOpen, setDropdownOpen] = useState(false);
  const inputRef = useRef<HTMLInputElement>(null);

  const { data: profiles, isFetching } = useSearchProfiles(query);

  const excludeSet = useMemo(() => new Set(excludePubkeys), [excludePubkeys]);
  const filteredProfiles = useMemo(
    () => (profiles ?? []).filter((p) => !excludeSet.has(p.pubkey)),
    [profiles, excludeSet],
  );

  useEffect(() => {
    if (query.trim().length > 0 && filteredProfiles.length > 0) {
      setDropdownOpen(true);
    } else if (query.trim().length === 0) {
      setDropdownOpen(false);
    }
  }, [filteredProfiles, query]);

  const handleSelect = useCallback((profile: SearchProfile) => {
    onAdd(profile);
    setQuery('');
    setDropdownOpen(false);
    inputRef.current?.focus();
  }, [onAdd]);

  return (
    <Popover open={dropdownOpen} onOpenChange={setDropdownOpen}>
      <PopoverTrigger asChild>
        <div className="relative flex items-center">
          <Search className="absolute left-3 size-4 text-muted-foreground pointer-events-none" />
          {isFetching && query.trim() && (
            <Loader2 className="absolute right-3 size-4 text-muted-foreground animate-spin" />
          )}
          <Input
            ref={inputRef}
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            onFocus={() => {
              if (query.trim().length > 0 && filteredProfiles.length > 0) {
                setDropdownOpen(true);
              }
            }}
            placeholder="Search people to add..."
            className="pl-10 pr-10 rounded-full bg-secondary border-0 focus-visible:ring-0 focus-visible:ring-offset-0 h-9 text-sm"
            autoComplete="off"
          />
        </div>
      </PopoverTrigger>

      <PopoverContent
        align="start"
        side="bottom"
        sideOffset={6}
        onOpenAutoFocus={(e) => e.preventDefault()}
        className="z-[270] w-[var(--radix-popover-trigger-width)] rounded-xl border-border p-0 shadow-lg overflow-hidden"
      >
        {filteredProfiles.length > 0 ? (
          <div className="max-h-[200px] overflow-y-auto py-1">
            {filteredProfiles.map((profile) => (
              <SearchResultItem key={profile.pubkey} profile={profile} onClick={handleSelect} />
            ))}
          </div>
        ) : query.trim().length >= 2 && !isFetching ? (
          <div className="py-4 text-center text-sm text-muted-foreground">
            No people found
          </div>
        ) : null}
      </PopoverContent>
    </Popover>
  );
}

/** A pending member chip with role toggle and remove button. */
function PendingMemberChip({
  pending,
  onRemove,
  onToggleRole,
}: {
  pending: PendingMember;
  onRemove: (pubkey: string) => void;
  onToggleRole?: (pubkey: string) => void;
}) {
  const { profile, role } = pending;
  const { metadata, pubkey } = profile;
  const displayName = metadata.display_name || metadata.name || genUserName(pubkey);

  return (
    <div className="flex items-center gap-2 p-2 rounded-lg bg-secondary/30 border border-border/50">
      <Avatar shape={getAvatarShape(metadata)} className="size-7 shrink-0">
        <AvatarImage src={metadata.picture} alt={displayName} />
        <AvatarFallback className="bg-primary/20 text-primary text-[10px]">
          {displayName[0]?.toUpperCase() || '?'}
        </AvatarFallback>
      </Avatar>
      <span className="flex-1 text-sm truncate">
        <EmojifiedText tags={profile.event.tags}>{displayName}</EmojifiedText>
      </span>

      {/* Role badge — clickable if founder can toggle */}
      <button
        type="button"
        onClick={onToggleRole ? () => onToggleRole(pubkey) : undefined}
        disabled={!onToggleRole}
        className={cn(
          'flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full shrink-0 transition-colors',
          role === 'moderator'
            ? 'bg-amber-500/10 text-amber-600 dark:text-amber-400'
            : 'bg-primary/10 text-primary',
          onToggleRole && 'cursor-pointer hover:opacity-80',
        )}
        title={onToggleRole ? 'Click to toggle role' : undefined}
      >
        {role === 'moderator' ? <Crown className="size-3" /> : <Users className="size-3" />}
        {role === 'moderator' ? 'Moderator' : 'Member'}
      </button>

      <button
        type="button"
        onClick={() => onRemove(pubkey)}
        className="shrink-0 size-6 rounded-full hover:bg-destructive/10 flex items-center justify-center transition-colors"
        title="Remove"
      >
        <X className="size-3.5 text-muted-foreground hover:text-destructive" />
      </button>
    </div>
  );
}

/** A profile search result row. */
function SearchResultItem({ profile, onClick }: { profile: SearchProfile; onClick: (profile: SearchProfile) => void }) {
  const { metadata, pubkey } = profile;
  const displayName = metadata.display_name || metadata.name || genUserName(pubkey);

  return (
    <button
      className="w-full flex items-center gap-3 px-3 py-2 text-left transition-colors cursor-pointer hover:bg-secondary/60"
      onClick={() => onClick(profile)}
      onMouseDown={(e) => e.preventDefault()}
    >
      <Avatar shape={getAvatarShape(metadata)} className="size-8 shrink-0">
        <AvatarImage src={metadata.picture} alt={displayName} />
        <AvatarFallback className="bg-primary/20 text-primary text-xs">
          {displayName[0]?.toUpperCase() || '?'}
        </AvatarFallback>
      </Avatar>
      <div className="flex-1 min-w-0">
        <span className="text-sm font-medium truncate block">
          <EmojifiedText tags={profile.event.tags}>{displayName}</EmojifiedText>
        </span>
        {metadata.nip05 && (
          <span className="text-xs text-muted-foreground truncate block">
            {metadata.nip05.startsWith('_@') ? metadata.nip05.slice(2) : metadata.nip05}
          </span>
        )}
      </div>
    </button>
  );
}
