import { Box, Button, color, config, Icon, IconButton, Icons, Spinner, Text, toRem } from 'folds';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useNavigate } from 'react-router';
import { VerificationRequest } from 'matrix-js-sdk/lib/crypto-api';
import { AsyncState, AsyncStatus, useAsync } from '../../hooks/useAsyncCallback';
import { VerificationStatus } from '../../hooks/useDeviceVerificationStatus';
import { DeviceVerificationStatus } from '../DeviceVerificationStatus';
import { DeviceVerification } from '../DeviceVerification';
import { useOtherUserDevices, UserDevice } from '../../hooks/useOtherUserDevices';
import { ContainerColor } from '../../styles/ContainerColor.css';
import { UserHero, UserHeroName } from './UserHero';
import { getMxIdServer, mxcUrlToHttp } from '../../utils/matrix';
import { getMemberAvatarMxc, getMemberDisplayName } from '../../utils/room';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { usePowerLevels } from '../../hooks/usePowerLevels';
import { useRoom } from '../../hooks/useRoom';
import { useUserPresence } from '../../hooks/useUserPresence';
import { IgnoredUserAlert, MutualRoomsChip, OptionsChip, ServerChip, ShareChip } from './UserChips';
import { useCrossSigningActive } from '../../hooks/useCrossSigning';
import { MemberVerificationBadge } from '../MemberVerificationBadge';
import { useCloseUserRoomProfile } from '../../state/hooks/userRoomProfile';
import { PowerChip } from './PowerChip';
import { UserInviteAlert, UserBanAlert, UserModeration, UserKickAlert } from './UserModeration';
import { useIgnoredUsers } from '../../hooks/useIgnoredUsers';
import { useMembership } from '../../hooks/useMembership';
import { Membership } from '../../../types/matrix/room';
import { useRoomCreators } from '../../hooks/useRoomCreators';
import { useRoomPermissions } from '../../hooks/useRoomPermissions';
import { useMemberPowerCompare } from '../../hooks/useMemberPowerCompare';
import { ReportUserModal } from '../../features/room/ReportUserModal';
import { CreatorChip } from './CreatorChip';
import { getDirectCreatePath, withSearchParam } from '../../pages/pathUtils';
import { DirectCreateSearchParams } from '../../pages/paths';
import { useExtendedProfile } from '../../hooks/useExtendedProfile';
import { useUserNotes, USER_NOTE_MAX_LENGTH } from '../../hooks/useUserNotes';

type VerifyDeviceButtonProps = {
  userId: string;
  deviceId: string;
};
function VerifyDeviceButton({ userId, deviceId }: VerifyDeviceButtonProps) {
  const mx = useMatrixClient();
  const [requestState, setRequestState] = useState<AsyncState<VerificationRequest, Error>>({
    status: AsyncStatus.Idle,
  });

  const requestVerification = useAsync<VerificationRequest, Error, []>(
    useCallback(() => {
      const crypto = mx.getCrypto();
      if (!crypto) return Promise.reject(new Error('Crypto unavailable'));
      return crypto.requestDeviceVerification(userId, deviceId);
    }, [mx, userId, deviceId]),
    setRequestState,
  );

  const handleExit = useCallback(() => setRequestState({ status: AsyncStatus.Idle }), []);

  const requesting = requestState.status === AsyncStatus.Loading;

  return (
    <>
      <Button
        size="300"
        variant="Warning"
        fill="Soft"
        radii="300"
        onClick={requestVerification}
        before={requesting && <Spinner size="100" variant="Warning" />}
        disabled={requesting}
      >
        <Text size="B300">Verify</Text>
      </Button>
      {requestState.status === AsyncStatus.Success && (
        <DeviceVerification request={requestState.data} onExit={handleExit} />
      )}
    </>
  );
}

type UserDeviceRowProps = {
  userId: string;
  device: UserDevice;
};
function UserDeviceRow({ userId, device }: UserDeviceRowProps) {
  const mx = useMatrixClient();
  const crypto = mx.getCrypto() ?? undefined;

  return (
    <DeviceVerificationStatus crypto={crypto} userId={userId} deviceId={device.deviceId}>
      {(status) => {
        const deviceColor =
          status === VerificationStatus.Verified
            ? color.Success.Main
            : status === VerificationStatus.Unverified
              ? color.Warning.Main
              : color.SurfaceVariant.OnContainer;
        return (
          <Box alignItems="Center" gap="200">
            <Icon size="100" src={Icons.ShieldUser} style={{ color: deviceColor, flexShrink: 0 }} />
            <Box grow="Yes" direction="Column" style={{ minWidth: 0 }}>
              <Text size="T300" truncate>
                {device.displayName ?? device.deviceId}
              </Text>
              {device.displayName && (
                <Text
                  size="T200"
                  truncate
                  style={{ color: color.SurfaceVariant.OnContainer, fontFamily: 'monospace' }}
                >
                  {device.deviceId}
                </Text>
              )}
            </Box>
            {status === VerificationStatus.Unverified && (
              <VerifyDeviceButton userId={userId} deviceId={device.deviceId} />
            )}
          </Box>
        );
      }}
    </DeviceVerificationStatus>
  );
}

type UserDeviceSessionsProps = {
  userId: string;
};
function UserDeviceSessions({ userId }: UserDeviceSessionsProps) {
  const devicesState = useOtherUserDevices(userId);
  const [expanded, setExpanded] = useState(false);

  if (devicesState.status === 'loading') {
    return (
      <Box alignItems="Center" gap="200" style={{ padding: config.space.S200, opacity: 0.6 }}>
        <Spinner size="100" variant="Secondary" />
        <Text size="T200">Loading sessions…</Text>
      </Box>
    );
  }

  if (devicesState.status === 'error') {
    return (
      <Box
        className={ContainerColor({ variant: 'Critical' })}
        alignItems="Center"
        gap="200"
        style={{ padding: config.space.S200, borderRadius: config.radii.R300 }}
      >
        <Icon size="100" src={Icons.Warning} />
        <Text size="T200">Could not load sessions.</Text>
      </Box>
    );
  }

  const devices = devicesState.devices;
  if (devices.length === 0) return null;

  return (
    <Box
      direction="Column"
      gap="100"
      style={{
        background: color.SurfaceVariant.Container,
        borderRadius: config.radii.R300,
        padding: config.space.S300,
      }}
    >
      <Box alignItems="Center" gap="200">
        <Box grow="Yes" alignItems="Center" gap="200">
          <Icon size="100" src={Icons.Monitor} style={{ flexShrink: 0 }} />
          <Text size="T300">
            <b>Sessions</b>
          </Text>
          <Text size="T200" style={{ color: color.SurfaceVariant.OnContainer }}>
            {devices.length}
          </Text>
        </Box>
        <IconButton
          size="300"
          radii="300"
          variant="SurfaceVariant"
          aria-label={expanded ? 'Collapse sessions' : 'Expand sessions'}
          onClick={() => setExpanded((v) => !v)}
        >
          <Icon
            size="100"
            src={expanded ? Icons.ChevronTop : Icons.ChevronBottom}
            style={{ flexShrink: 0 }}
          />
        </IconButton>
      </Box>
      {expanded && (
        <Box direction="Column" gap="200" style={{ paddingTop: config.space.S100 }}>
          {devices.map((device) => (
            <Box
              key={device.deviceId}
              style={{
                paddingTop: config.space.S100,
                paddingBottom: config.space.S100,
                borderTop: `${toRem(1)} solid ${color.SurfaceVariant.ContainerLine}`,
              }}
            >
              <UserDeviceRow userId={userId} device={device} />
            </Box>
          ))}
        </Box>
      )}
    </Box>
  );
}

function UserPrivateNotes({ userId }: { userId: string }) {
  const { getNote, setNote } = useUserNotes();
  const [draft, setDraft] = useState(() => getNote(userId));
  const [saving, setSaving] = useState(false);
  const saveTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);

  // Sync if account data arrives after mount
  useEffect(() => {
    setDraft(getNote(userId));
  }, [getNote, userId]);

  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
    const val = e.target.value;
    setDraft(val);
    clearTimeout(saveTimer.current);
    saveTimer.current = setTimeout(async () => {
      setSaving(true);
      await setNote(userId, val);
      setSaving(false);
    }, 800);
  };

  useEffect(() => () => clearTimeout(saveTimer.current), []);

  const charsLeft = USER_NOTE_MAX_LENGTH - draft.length;

  return (
    <Box direction="Column" gap="200">
      <Box justifyContent="SpaceBetween" alignItems="Center" gap="200">
        <Text size="L400">Private Note</Text>
        {saving ? (
          <Box alignItems="Center" gap="100" shrink="No">
            <Spinner variant="Success" fill="Solid" size="100" />
            <Text size="T200" priority="400">
              Saving…
            </Text>
          </Box>
        ) : (
          <Text size="T200" priority="400">
            {charsLeft < 100 ? `${charsLeft} left` : ''}
          </Text>
        )}
      </Box>
      <textarea
        aria-label="Private note about this user"
        value={draft}
        onChange={handleChange}
        maxLength={USER_NOTE_MAX_LENGTH}
        placeholder="Notes only visible to you…"
        rows={3}
        style={{
          width: '100%',
          resize: 'vertical',
          background: color.SurfaceVariant.Container,
          color: 'inherit',
          border: `${config.borderWidth.B300} solid ${color.SurfaceVariant.ContainerLine}`,
          borderRadius: config.radii.R300,
          padding: `${config.space.S200} ${config.space.S300}`,
          fontFamily: 'inherit',
          lineHeight: 1.5,
          boxSizing: 'border-box',
          outline: 'none',
        }}
      />
    </Box>
  );
}

type UserRoomProfileProps = {
  userId: string;
};
export function UserRoomProfile({ userId }: UserRoomProfileProps) {
  const mx = useMatrixClient();
  const [reportUserOpen, setReportUserOpen] = useState(false);
  const crossSigningActive = useCrossSigningActive();
  const useAuthentication = useMediaAuthentication();
  const navigate = useNavigate();
  const closeUserRoomProfile = useCloseUserRoomProfile();
  const ignoredUsers = useIgnoredUsers();
  const ignored = ignoredUsers.includes(userId);

  const room = useRoom();
  const showEncryption = crossSigningActive;
  const powerLevels = usePowerLevels(room);
  const creators = useRoomCreators(room);

  const permissions = useRoomPermissions(creators, powerLevels);
  const { hasMorePower } = useMemberPowerCompare(creators, powerLevels);

  const myUserId = mx.getSafeUserId();
  const creator = creators.has(userId);

  const canKickUser = permissions.action('kick', myUserId) && hasMorePower(myUserId, userId);
  const canBanUser = permissions.action('ban', myUserId) && hasMorePower(myUserId, userId);
  const canUnban = permissions.action('ban', myUserId);
  const canInvite = permissions.action('invite', myUserId);

  const member = room.getMember(userId);
  const membership = useMembership(room, userId);

  const server = getMxIdServer(userId);
  const displayName = getMemberDisplayName(room, userId);
  const avatarMxc = getMemberAvatarMxc(room, userId);
  const avatarUrl = (avatarMxc && mxcUrlToHttp(mx, avatarMxc, useAuthentication)) ?? undefined;

  const presence = useUserPresence(userId);
  const extProfile = useExtendedProfile(userId);

  const handleMessage = () => {
    closeUserRoomProfile();
    const directSearchParam: DirectCreateSearchParams = {
      userId,
    };
    navigate(withSearchParam(getDirectCreatePath(), directSearchParam));
  };

  return (
    <Box direction="Column">
      <UserHero
        userId={userId}
        avatarUrl={avatarUrl}
        presence={presence && presence.lastActiveTs !== 0 ? presence : undefined}
      />
      <Box direction="Column" gap="500" style={{ padding: config.space.S400 }}>
        <Box direction="Column" gap="400">
          <Box gap="400" alignItems="Center">
            <UserHeroName
              displayName={displayName}
              userId={userId}
              status={presence?.status}
              pronouns={extProfile.pronouns}
              timezone={extProfile.timezone}
            />
            {showEncryption && <MemberVerificationBadge userId={userId} />}
            {userId !== myUserId && (
              <Box shrink="No">
                <Button
                  size="300"
                  variant="Primary"
                  fill="Solid"
                  radii="300"
                  before={<Icon size="50" src={Icons.Message} filled />}
                  onClick={handleMessage}
                >
                  <Text size="B300">Message</Text>
                </Button>
              </Box>
            )}
          </Box>
          <Box alignItems="Center" gap="200" wrap="Wrap">
            {server && <ServerChip server={server} />}
            <ShareChip userId={userId} />
            {creator ? <CreatorChip /> : <PowerChip userId={userId} />}
            {userId !== myUserId && <MutualRoomsChip userId={userId} />}
            {userId !== myUserId && <OptionsChip userId={userId} />}
          </Box>
        </Box>
        {ignored && <IgnoredUserAlert />}
        {member && membership === Membership.Ban && (
          <UserBanAlert
            userId={userId}
            reason={member.events.member?.getContent().reason}
            canUnban={canUnban}
            bannedBy={member.events.member?.getSender()}
            ts={member.events.member?.getTs()}
          />
        )}
        {member &&
          membership === Membership.Leave &&
          member.events.member &&
          member.events.member.getSender() !== userId && (
            <UserKickAlert
              reason={member.events.member?.getContent().reason}
              kickedBy={member.events.member?.getSender()}
              ts={member.events.member?.getTs()}
            />
          )}
        {member && membership === Membership.Invite && (
          <UserInviteAlert
            userId={userId}
            reason={member.events.member?.getContent().reason}
            canKick={canKickUser}
            invitedBy={member.events.member?.getSender()}
            ts={member.events.member?.getTs()}
          />
        )}
        <UserModeration
          userId={userId}
          canInvite={canInvite && membership === Membership.Leave}
          canKick={canKickUser && membership === Membership.Join}
          canBan={canBanUser && membership !== Membership.Ban}
        />
        {userId !== myUserId && (
          <Box style={{ padding: `0 ${config.space.S400} ${config.space.S200}` }}>
            <Button
              variant="Critical"
              fill="None"
              size="300"
              radii="300"
              before={<Icon size="50" src={Icons.Warning} />}
              onClick={() => setReportUserOpen(true)}
            >
              <Text size="B300">Report User</Text>
            </Button>
          </Box>
        )}
        {showEncryption && userId !== myUserId && <UserDeviceSessions userId={userId} />}
        {userId !== myUserId && <UserPrivateNotes userId={userId} />}
        {reportUserOpen && (
          <ReportUserModal userId={userId} onClose={() => setReportUserOpen(false)} />
        )}
      </Box>
    </Box>
  );
}
