/* eslint-disable no-console */
import React, { useCallback, useEffect, useState } from 'react';
import { MatrixEvent, Room, RoomStateEvent } from 'matrix-js-sdk';
import { Box, Icon, IconButton, Icons, Text, color } from 'folds';
import { useMatrixClient } from '../../hooks/useMatrixClient';
import { useCall } from './useCall';
import { CallType } from './types';
import { getCallSounds, CallSoundType } from './CallSounds';
import { getMemberDisplayName, getMemberAvatarMxc } from '../../utils/room';
import { getMxIdLocalPart, mxcUrlToHttp } from '../../utils/matrix';
import { useMediaAuthentication } from '../../hooks/useMediaAuthentication';
import { UserAvatar } from '../../components/user-avatar';
import * as css from './IncomingCallNotification.css';

/** The Matrix state event type for call membership (MSC3401) */
const CALL_MEMBER_EVENT_TYPE = 'org.matrix.msc3401.call.member';

/** Represents an incoming call */
interface IncomingCall {
  roomId: string;
  roomName: string;
  callerIds: string[];
  startTime: number;
}

/**
 * Checks if a room is a DM or small group (not a server/space room)
 * @param room - The Matrix room to check
 * @returns true if the room is a DM or small group
 */
function isDirectOrSmallGroup(room: Room): boolean {
  if (room.isSpaceRoom()) {
    return false;
  }
  const memberCount = room.getJoinedMemberCount();
  return memberCount <= 10;
}

/**
 * Gets active call members from a room who are NOT the current user
 * Also returns the earliest call start time
 * @param room - The Matrix room
 * @param myUserId - The current user's ID
 * @returns Object with member IDs and earliest call start time
 */
function getOtherCallMembersWithTime(room: Room, myUserId: string): { members: string[]; earliestTime: number } {
  const members: string[] = [];
  const now = Date.now();
  let earliestTime = now;

  try {
    const stateEvents = room.currentState.getStateEvents(CALL_MEMBER_EVENT_TYPE);
    
    stateEvents.forEach((event) => {
      const content = event.getContent();
      const userId = event.getStateKey();
      
      if (!userId || userId === myUserId || !content['m.calls']) return;

      const calls = content['m.calls'];
      if (!Array.isArray(calls)) return;

      calls.forEach((call) => {
        const devices = call['m.devices'];
        if (!Array.isArray(devices)) return;

        devices.forEach((device) => {
          const expiresTs = device.expires_ts || 0;
          
          if (expiresTs > now && !members.includes(userId)) {
            members.push(userId);
            // Track event origin time to determine call age
            const eventTime = event.getTs() || now;
            if (eventTime < earliestTime) {
              earliestTime = eventTime;
            }
          }
        });
      });
    });
  } catch (e) {
    console.error('Error reading call member events:', e);
  }

  return { members, earliestTime };
}

/**
 * Wrapper for backward compatibility
 */
function getOtherCallMembers(room: Room, myUserId: string): string[] {
  return getOtherCallMembersWithTime(room, myUserId).members;
}

/**
 * Component that monitors for incoming calls and displays a notification UI
 * with room info, caller details, and accept/decline buttons.
 */
export function IncomingCallNotification() {
  const mx = useMatrixClient();
  const useAuthentication = useMediaAuthentication();
  const { activeCall, startCall } = useCall();
  const [incomingCall, setIncomingCall] = useState<IncomingCall | null>(null);
  const [declinedRooms, setDeclinedRooms] = useState<Set<string>>(new Set());
  const [seenCalls, setSeenCalls] = useState<Map<string, number>>(new Map());

  /**
   * Checks all rooms for incoming calls
   */
  const checkForIncomingCalls = useCallback(() => {
    const myUserId = mx.getUserId();
    if (!myUserId) return;

    // If we're already in a call, don't show incoming call notification
    if (activeCall) {
      setIncomingCall(null);
      return;
    }

    // Check all joined rooms for calls
    const rooms = mx.getRooms();
    const now = Date.now();
    const MAX_CALL_AGE = 10000; // 10 seconds
    
    // Find the first room with an incoming call
    const incomingRoom = rooms.find((room) => {
      // Only monitor 1-1 DMs (exactly 2 members)
      if (room.getJoinedMemberCount() !== 2) return false;
      // Skip rooms we've declined
      if (declinedRooms.has(room.roomId)) return false;
      const { members, earliestTime } = getOtherCallMembersWithTime(room, myUserId);
      if (members.length === 0) return false;
      
      // Skip if we've already seen this exact call session
      const lastSeenTime = seenCalls.get(room.roomId);
      if (lastSeenTime && lastSeenTime === earliestTime) return false;
      
      // Only show if the call is fresh (less than 10 seconds old)
      const callAge = now - earliestTime;
      return callAge < MAX_CALL_AGE;
    });

    if (incomingRoom) {
      const { members, earliestTime } = getOtherCallMembersWithTime(incomingRoom, myUserId);
      setIncomingCall({
        roomId: incomingRoom.roomId,
        roomName: incomingRoom.name || 'Unknown Room',
        callerIds: members,
        startTime: earliestTime,
      });
    } else {
      setIncomingCall(null);
    }
  }, [mx, activeCall, declinedRooms, seenCalls]);

  /**
   * Handles state events for call member changes
   */
  const handleStateEvent = useCallback((event: MatrixEvent) => {
    if (event.getType() === CALL_MEMBER_EVENT_TYPE) {
      // Clear declined status and seen calls if call ended in that room
      const roomId = event.getRoomId();
      if (roomId) {
        const room = mx.getRoom(roomId);
        if (room) {
          const myUserId = mx.getUserId();
          if (myUserId) {
            const members = getOtherCallMembers(room, myUserId);
            if (members.length === 0) {
              // Call ended, clear tracking
              setDeclinedRooms((prev) => {
                const newSet = new Set(prev);
                newSet.delete(roomId);
                return newSet;
              });
              setSeenCalls((prev) => {
                const newMap = new Map(prev);
                newMap.delete(roomId);
                return newMap;
              });
            }
          }
        }
      }
      checkForIncomingCalls();
    }
  }, [checkForIncomingCalls, declinedRooms, mx]);

  // Set up event listeners
  useEffect(() => {
    mx.on(RoomStateEvent.Events, handleStateEvent);
    checkForIncomingCalls();
    const interval = setInterval(checkForIncomingCalls, 10000);

    return () => {
      mx.off(RoomStateEvent.Events, handleStateEvent);
      clearInterval(interval);
    };
  }, [mx, handleStateEvent, checkForIncomingCalls]);

  // Play/stop dialing sound based on incoming call state
  // Mark call as seen when displayed
  useEffect(() => {
    if (incomingCall) {
      // Mark this call as seen so we don't notify again
      setSeenCalls((prev) => new Map(prev).set(incomingCall.roomId, incomingCall.startTime));
      
      const callAge = Date.now() - incomingCall.startTime;
      const MAX_CALL_AGE_FOR_SOUND = 15000; // 15 seconds
      
      if (callAge < MAX_CALL_AGE_FOR_SOUND) {
        getCallSounds().playSound(CallSoundType.CallDialing, true);
        console.log('🔔 Incoming call from room:', incomingCall.roomName, '(age:', Math.round(callAge / 1000), 's)');
      } else {
        console.log('📞 Incoming call too old for sound:', incomingCall.roomName, '(age:', Math.round(callAge / 1000), 's)');
        getCallSounds().stopDialingLoop();
      }
    } else {
      getCallSounds().stopDialingLoop();
    }

    return () => {
      getCallSounds().stopDialingLoop();
    };
  }, [incomingCall]);

  /**
   * Handles accepting the incoming call
   */
  const handleAccept = useCallback(async () => {
    if (!incomingCall) return;
    
    getCallSounds().stopDialingLoop();
    setSeenCalls((prev) => new Map(prev).set(incomingCall.roomId, incomingCall.startTime));
    try {
      await startCall(incomingCall.roomId, CallType.Voice);
    } catch (error) {
      console.error('Failed to join call:', error);
    }
  }, [incomingCall, startCall]);

  /**
   * Handles declining the incoming call
   */
  const handleDecline = useCallback(() => {
    if (!incomingCall) return;
    
    getCallSounds().stopDialingLoop();
    setSeenCalls((prev) => new Map(prev).set(incomingCall.roomId, incomingCall.startTime));
    setDeclinedRooms((prev) => new Set(prev).add(incomingCall.roomId));
    setIncomingCall(null);
  }, [incomingCall]);

  // Don't render if no incoming call
  if (!incomingCall) {
    return null;
  }

  const room = mx.getRoom(incomingCall.roomId);
  const firstCaller = incomingCall.callerIds[0];
  const isGroupCall = room ? room.getJoinedMemberCount() > 2 : false;
  
  /**
   * Gets the display name for a user
   */
  const getDisplayName = (userId: string): string => {
    if (!room) return getMxIdLocalPart(userId) ?? userId;
    return getMemberDisplayName(room, userId) ?? getMxIdLocalPart(userId) ?? userId;
  };

  /**
   * Gets the avatar URL for a user
   */
  const getAvatarUrl = (userId: string): string | undefined => {
    if (!room) return undefined;
    const mxcUrl = getMemberAvatarMxc(room, userId);
    if (!mxcUrl) return undefined;
    return mxcUrlToHttp(mx, mxcUrl, useAuthentication, 48, 48, 'crop') ?? undefined;
  };

  const callerName = getDisplayName(firstCaller);
  const avatarUrl = getAvatarUrl(firstCaller);
  const otherCallersCount = incomingCall.callerIds.length - 1;

  return (
    <div className={css.IncomingCallContainer}>
      <div className={css.IncomingCallContent}>
        <div className={css.CallerInfo}>
          <div className={css.CallerAvatarWrapper}>
            <UserAvatar
              className={css.CallerAvatar}
              userId={firstCaller}
              src={avatarUrl}
              alt={callerName}
              renderFallback={() => (
                <Text size="H5" style={{ color: color.Surface.Container }}>
                  {callerName.charAt(0).toUpperCase()}
                </Text>
              )}
            />
            <div className={css.CallerAvatarRing} />
          </div>
          <Box direction="Column" gap="100">
            <Text size="T300" className={css.CallerName}>
              {callerName}
              {otherCallersCount > 0 && (
                <span className={css.OtherCallers}>
                  {` +${otherCallersCount} other${otherCallersCount > 1 ? 's' : ''}`}
                </span>
              )}
            </Text>
            {!isGroupCall && (
              <Text size="T200" className={css.RoomName}>
                <Icon size="100" src={Icons.Phone} />
                <span>Calling from {incomingCall.roomName}</span>
              </Text>
            )}
          </Box>
        </div>

        <Box alignItems="Center" gap="200" className={css.CallActions}>
          <IconButton
            onClick={handleDecline}
            variant="Critical"
            className={css.DeclineButton}
          >
            <Icon size="300" src={Icons.Cross} />
          </IconButton>
          <IconButton
            onClick={handleAccept}
            variant="Success"
            className={css.AcceptButton}
          >
            <Icon size="300" src={Icons.Phone} />
          </IconButton>
        </Box>
      </div>
    </div>
  );
}
