import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import {
  Box,
  Header,
  IconButton,
  Scroll,
  Spinner,
  Text,
  as,
  config,
  Chip,
  Avatar,
} from 'folds';
import { MsgType, Room } from 'matrix-js-sdk';
import { useAtom } from 'jotai';
import classNames from 'classnames';
import { Icon, Icons } from '../../../components/icons';
import * as css from './RoomMediaMenu.css';
import { RoomMediaItem, useRoomMediaEvents } from '../../../hooks/useRoomMediaEvents';
import { useMatrixClient } from '../../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
import {
  decryptFile,
  downloadEncryptedMedia,
  getMxIdLocalPart,
  isAuthenticatedMediaUrl,
  mxcUrlToHttp,
} from '../../../utils/matrix';
import { useRoomNavigate } from '../../../hooks/useRoomNavigate';
import { Image } from '../../../components/media';
import { AsyncStatus, useAsyncCallback } from '../../../hooks/useAsyncCallback';
import { getCurrentAccessToken } from '../../../utils/auth';
import { FALLBACK_MIMETYPE } from '../../../utils/mimeTypes';
import { VideoFrameThumbnail } from '../../../components/message/content/VideoFrameThumbnail';
import { getMemberAvatarMxc, getMemberDisplayName } from '../../../utils/room';
import { UserAvatar } from '../../../components/user-avatar';
import {
  getMediaDrawerScrollTop,
  isMediaDrawerAtom,
  setMediaDrawerScrollTop,
} from '../../../state/mediaDrawer';
import { ContainerColor } from '../../../styles/ContainerColor.css';

const fetchAuthenticatedMedia = async (
  url: string,
  accessToken: string | null
): Promise<string> => {
  try {
    let response = await fetch(url, {
      method: 'GET',
      headers: accessToken ? { Authorization: `Bearer ${accessToken}` } : undefined,
    });

    if (!response.ok && response.status === 401 && accessToken) {
      response = await fetch(url, { method: 'GET' });
    }

    if (!response.ok) return url;
    const blob = await response.blob();
    return URL.createObjectURL(blob);
  } catch {
    return url;
  }
};

type MediaTileThumbProps = {
  item: RoomMediaItem;
};

function MediaTileImageThumb({ item }: { item: RoomMediaItem & { thumbMxc: string } }) {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();

  const [srcState, loadSrc] = useAsyncCallback(
    useCallback(async () => {
      const accessToken = getCurrentAccessToken();

      if (item.thumbEncInfo) {
        const mediaUrl = mxcUrlToHttp(mx, item.thumbMxc, useAuthentication) ?? item.thumbMxc;
        const fileContent = await downloadEncryptedMedia(
          mediaUrl,
          (encBuf) =>
            decryptFile(encBuf, item.thumbMimeType ?? FALLBACK_MIMETYPE, item.thumbEncInfo!),
          accessToken
        );
        return URL.createObjectURL(fileContent);
      }

      const mediaUrl =
        mxcUrlToHttp(mx, item.thumbMxc, useAuthentication, 240, 240, 'crop') ?? item.thumbMxc;

      if (useAuthentication && isAuthenticatedMediaUrl(mediaUrl)) {
        return fetchAuthenticatedMedia(mediaUrl, accessToken);
      }

      return mediaUrl;
    }, [mx, item.thumbMxc, item.thumbEncInfo, item.thumbMimeType, useAuthentication])
  );

  useEffect(() => {
    loadSrc();
  }, [loadSrc]);

  useEffect(() => {
    if (srcState.status !== AsyncStatus.Success) return undefined;
    const src = srcState.data;
    if (!src.startsWith('blob:')) return undefined;
    return () => URL.revokeObjectURL(src);
  }, [srcState]);

  if (srcState.status === AsyncStatus.Success) {
    return <Image className={css.MediaTileImg} src={srcState.data} alt={item.body} loading="lazy" />;
  }

  return (
    <Box alignItems="Center" justifyContent="Center" style={{ width: '100%', height: '100%' }}>
      {srcState.status === AsyncStatus.Loading ? (
        <Spinner size="200" />
      ) : (
        <Icon src={Icons.Photo} size="400" />
      )}
    </Box>
  );
}

function MediaTileFullImageThumb({ item }: { item: RoomMediaItem }) {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();

  const [srcState, loadSrc] = useAsyncCallback(
    useCallback(async () => {
      const accessToken = getCurrentAccessToken();
      const mediaUrl = mxcUrlToHttp(mx, item.fullMxc, useAuthentication) ?? item.fullMxc;

      if (item.fullEncInfo) {
        const fileContent = await downloadEncryptedMedia(
          mediaUrl,
          (encBuf) =>
            decryptFile(encBuf, item.fullMimeType ?? FALLBACK_MIMETYPE, item.fullEncInfo!),
          accessToken
        );
        return URL.createObjectURL(fileContent);
      }

      if (useAuthentication && isAuthenticatedMediaUrl(mediaUrl)) {
        return fetchAuthenticatedMedia(mediaUrl, accessToken);
      }

      return mediaUrl;
    }, [mx, item.fullMxc, item.fullEncInfo, item.fullMimeType, useAuthentication])
  );

  useEffect(() => {
    loadSrc();
  }, [loadSrc]);

  useEffect(() => {
    if (srcState.status !== AsyncStatus.Success) return undefined;
    const src = srcState.data;
    if (!src.startsWith('blob:')) return undefined;
    return () => URL.revokeObjectURL(src);
  }, [srcState]);

  if (srcState.status === AsyncStatus.Success) {
    return <Image className={css.MediaTileImg} src={srcState.data} alt={item.body} loading="lazy" />;
  }

  return (
    <Box alignItems="Center" justifyContent="Center" style={{ width: '100%', height: '100%' }}>
      {srcState.status === AsyncStatus.Loading ? (
        <Spinner size="200" />
      ) : (
        <Icon src={Icons.Photo} size="400" />
      )}
    </Box>
  );
}

function MediaTileThumb({ item }: MediaTileThumbProps) {
  if (item.thumbMxc) {
    return <MediaTileImageThumb item={{ ...item, thumbMxc: item.thumbMxc }} />;
  }

  if (item.msgtype === MsgType.Video) {
    return (
      <Box style={{ width: '100%', height: '100%', position: 'relative' }}>
        <Box
          alignItems="Center"
          justifyContent="Center"
          style={{ position: 'absolute', inset: 0 }}
        >
          <Spinner size="200" />
        </Box>
        <VideoFrameThumbnail
          mimeType={item.fullMimeType ?? 'video/mp4'}
          url={item.fullMxc}
          encInfo={item.fullEncInfo}
          renderImage={(src) => (
            <Image className={css.MediaTileImg} src={src} alt={item.body} loading="lazy" />
          )}
        />
      </Box>
    );
  }

  return <MediaTileFullImageThumb item={item} />;
}

type MediaDayGroup = {
  dayKey: string;
  dayLabel: string;
  items: RoomMediaItem[];
};

type MediaMonthGroup = {
  monthKey: string;
  monthLabel: string;
  days: MediaDayGroup[];
};

const monthFormatter = new Intl.DateTimeFormat(undefined, {
  month: 'long',
  year: 'numeric',
});

const dayFormatter = new Intl.DateTimeFormat(undefined, {
  weekday: 'short',
  month: 'short',
  day: 'numeric',
});

const sameDayFormatter = new Intl.DateTimeFormat(undefined, {
  year: 'numeric',
  month: '2-digit',
  day: '2-digit',
});

const formatDayLabel = (ts: number): string => {
  const date = new Date(ts);
  const today = new Date();
  const yesterday = new Date();
  yesterday.setDate(today.getDate() - 1);

  if (sameDayFormatter.format(date) === sameDayFormatter.format(today)) return 'Today';
  if (sameDayFormatter.format(date) === sameDayFormatter.format(yesterday)) return 'Yesterday';
  return dayFormatter.format(date);
};

const groupMediaByMonthAndDay = (items: RoomMediaItem[]): MediaMonthGroup[] => {
  const months: MediaMonthGroup[] = [];

  for (const item of items) {
    const date = new Date(item.ts);
    const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`;
    const dayKey = `${monthKey}-${String(date.getDate()).padStart(2, '0')}`;

    let month = months[months.length - 1];
    if (!month || month.monthKey !== monthKey) {
      month = {
        monthKey,
        monthLabel: monthFormatter.format(date),
        days: [],
      };
      months.push(month);
    }

    let day = month.days[month.days.length - 1];
    if (!day || day.dayKey !== dayKey) {
      day = {
        dayKey,
        dayLabel: formatDayLabel(item.ts),
        items: [],
      };
      month.days.push(day);
    }

    day.items.push(item);
  }

  return months;
};

type MediaSenderChip = {
  userId: string;
  name: string;
  avatarMxc?: string;
  count: number;
};

export type MediaDrawerProps = {
  room: Room;
  /** Full-page takeover when the layout is too narrow for a side rail. */
  solo?: boolean;
};

export const MediaDrawer = as<'div', MediaDrawerProps>(({ room, solo = false, className, ...props }, ref) => {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const { navigateRoom } = useRoomNavigate();
  const [, setMediaDrawer] = useAtom(isMediaDrawerAtom);
  const { items, loading, hasMore, loadMore } = useRoomMediaEvents(room);
  const [excludedSenders, setExcludedSenders] = useState<Set<string>>(() => new Set());
  const scrollRef = useRef<HTMLDivElement>(null);
  const restoredScrollRef = useRef(false);

  const senders = useMemo<MediaSenderChip[]>(() => {
    const counts = new Map<string, number>();
    for (const item of items) {
      counts.set(item.sender, (counts.get(item.sender) ?? 0) + 1);
    }

    return Array.from(counts.entries())
      .map(([userId, count]) => ({
        userId,
        count,
        name: getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId,
        avatarMxc: getMemberAvatarMxc(room, userId),
      }))
      .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
  }, [items, room]);

  const filteredItems = useMemo(
    () => items.filter((item) => !excludedSenders.has(item.sender)),
    [items, excludedSenders]
  );
  const sections = useMemo(() => groupMediaByMonthAndDay(filteredItems), [filteredItems]);

  useLayoutEffect(() => {
    restoredScrollRef.current = false;
  }, [room.roomId]);

  useLayoutEffect(() => {
    if (restoredScrollRef.current || items.length === 0) return;
    const el = scrollRef.current;
    if (!el) return;
    const saved = getMediaDrawerScrollTop(room.roomId);
    if (typeof saved === 'number') {
      el.scrollTop = saved;
    }
    restoredScrollRef.current = true;
  }, [room.roomId, items.length]);

  const handleClose = useCallback(() => {
    setMediaDrawer(false);
  }, [setMediaDrawer]);

  const handleSenderClick = useCallback(
    (evt: React.MouseEvent, userId: string) => {
      if (evt.ctrlKey || evt.metaKey) {
        evt.preventDefault();
        const allIds = senders.map((sender) => sender.userId);
        setExcludedSenders((prev) => {
          const enabledIds = allIds.filter((id) => !prev.has(id));
          const alreadySolo = enabledIds.length === 1 && enabledIds[0] === userId;
          if (alreadySolo) return new Set();
          return new Set(allIds.filter((id) => id !== userId));
        });
        return;
      }

      setExcludedSenders((prev) => {
        const next = new Set(prev);
        if (next.has(userId)) next.delete(userId);
        else next.add(userId);
        return next;
      });
    },
    [senders]
  );

  const handleOpen = useCallback(
    (eventId: string) => {
      navigateRoom(room.roomId, eventId);
      if (solo) setMediaDrawer(false);
    },
    [navigateRoom, room.roomId, solo, setMediaDrawer]
  );

  const handleScroll = useCallback(
    (evt: React.UIEvent<HTMLDivElement>) => {
      const el = evt.currentTarget;
      setMediaDrawerScrollTop(room.roomId, el.scrollTop);
      const nearBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - 80;
      if (nearBottom && hasMore && !loading) {
        loadMore();
      }
    },
    [hasMore, loading, loadMore, room.roomId]
  );

  return (
    <Box
      shrink="No"
      grow={solo ? 'Yes' : undefined}
      direction="Column"
      className={classNames(
        solo ? css.MediaDrawerSolo : css.MediaDrawer,
        ContainerColor({ variant: 'Background' }),
        className
      )}
      {...props}
      ref={ref}
    >
      <Header className={css.MediaDrawerHeader} size="600" variant="Background">
        <Box grow="Yes" alignItems="Center">
          <Text size="H5" truncate>
            Shared Media
          </Text>
        </Box>
        <IconButton size="300" onClick={handleClose} radii="300" aria-label="Close">
          <Icon src={Icons.Cross} size="100" />
        </IconButton>
      </Header>

      {senders.length > 0 && (
        <div className={css.MediaUserFiltersScroll}>
          <div className={css.MediaUserFilters}>
            {senders.map((sender) => {
              const enabled = !excludedSenders.has(sender.userId);
              const avatarUrl = sender.avatarMxc
                ? mxcUrlToHttp(mx, sender.avatarMxc, useAuthentication, 48, 48, 'crop') ??
                  undefined
                : undefined;

              return (
                <Chip
                  key={sender.userId}
                  className={css.MediaUserChip}
                  variant={enabled ? 'Primary' : 'SurfaceVariant'}
                  outlined
                  radii="Pill"
                  aria-pressed={enabled}
                  onClick={(evt) => handleSenderClick(evt, sender.userId)}
                  before={
                    <Avatar size="200">
                      <UserAvatar
                        userId={sender.userId}
                        src={avatarUrl}
                        alt={sender.name}
                        renderFallback={() => (
                          <Text as="span" size="L400">
                            {sender.name.slice(0, 1).toUpperCase()}
                          </Text>
                        )}
                      />
                    </Avatar>
                  }
                >
                  <Text className={css.MediaUserChipLabel} size="T200" truncate>
                    {sender.name}
                  </Text>
                </Chip>
              );
            })}
          </div>
        </div>
      )}

      <Box grow="Yes" direction="Column" className={css.MediaDrawerContentBase}>
        <Scroll
          ref={scrollRef}
          visibility="Hover"
          hideTrack
          variant="Background"
          onScroll={handleScroll}
        >
          <div className={css.MediaDrawerContent}>
            {items.length === 0 && !loading && (
              <Text className={css.MediaEmpty} size="T300" priority="300">
                No shared photos or videos yet.
              </Text>
            )}

            {items.length > 0 && filteredItems.length === 0 && (
              <Text className={css.MediaEmpty} size="T300" priority="300">
                No media from the selected people.
              </Text>
            )}

            {sections.map((month) => (
              <section key={month.monthKey} className={css.MediaMonthSection}>
                <Text className={css.MediaMonthHeader} size="H6" as="h3">
                  {month.monthLabel}
                </Text>
                {month.days.map((day) => (
                  <div key={day.dayKey} className={css.MediaDaySection}>
                    <Text className={css.MediaDayHeader} size="T200" priority="300" as="h4">
                      {day.dayLabel}
                    </Text>
                    <div className={css.MediaGrid}>
                      {day.items.map((item) => (
                        <button
                          key={item.eventId}
                          type="button"
                          className={css.MediaTile}
                          onClick={() => handleOpen(item.eventId)}
                          title={item.body}
                          aria-label={item.body}
                        >
                          <MediaTileThumb item={item} />
                          {item.msgtype === MsgType.Video && (
                            <span className={css.MediaTileBadge}>
                              <Icon src={Icons.Play} size="100" />
                            </span>
                          )}
                        </button>
                      ))}
                    </div>
                  </div>
                ))}
              </section>
            ))}

            {(loading || hasMore) && (
              <div className={css.MediaFooter} style={{ minHeight: config.space.S700 }}>
                {loading && <Spinner size="400" />}
              </div>
            )}
          </div>
        </Scroll>
      </Box>
    </Box>
  );
});
