import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

/**
 * E2EE Session Manager Edge Function
 *
 * This function handles session management, cleanup, and recovery for the
 * end-to-end encryption system. It operates on metadata only and cannot
 * access the actual encrypted message content.
 *
 * Security Properties:
 * - Blind to message content (only handles metadata)
 * - Enforces proper session protocols
 * - Handles cleanup and recovery scenarios
 * - Maintains perfect forward secrecy
 */

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'POST, OPTIONS',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
  'Content-Type': 'application/json',
};

serve(async (req) => {
  // Handle CORS preflight
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders });
  }

  try {
    // Verify authorization header exists
    const authHeader = req.headers.get('Authorization');
    if (!authHeader) {
      return new Response(JSON.stringify({ error: 'Missing authorization header' }), {
        status: 401,
        headers: corsHeaders,
      });
    }

    // Create auth client to verify the user's JWT
    const authClient = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      Deno.env.get('SUPABASE_ANON_KEY') ?? '',
      {
        global: {
          headers: {
            Authorization: authHeader,
            apikey: Deno.env.get('SUPABASE_ANON_KEY') ?? '',
          },
        },
      }
    );

    // Verify the JWT and get the authenticated user
    const { data: { user }, error: authError } = await authClient.auth.getUser();
    if (authError || !user) {
      console.error('[E2EE Session Manager] Auth error:', authError);
      return new Response(JSON.stringify({ error: 'Unauthorized' }), {
        status: 401,
        headers: corsHeaders,
      });
    }

    const serviceRoleKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY');
    if (!serviceRoleKey) {
      console.error('[E2EE Session Manager] Missing SUPABASE_SERVICE_ROLE_KEY');
      return new Response(JSON.stringify({ error: 'Server misconfiguration: missing service role key' }), {
        status: 500,
        headers: corsHeaders,
      });
    }

    // Create service client for database operations (bypasses RLS)
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      serviceRoleKey
    );

    // Parse request body
    const { action, userId, recipientId, conversationId, hasSession } = await req.json();

    if (!action) {
      return new Response(JSON.stringify({ error: 'Action is required' }), {
        status: 400,
        headers: corsHeaders,
      });
    }

    // Use the authenticated user's ID, ignoring any userId in the request body
    // This prevents users from impersonating others
    const authenticatedUserId = user.id;

    console.log(`[E2EE Session Manager] ${action} requested by ${authenticatedUserId}`);

    switch (action) {
      case 'reset_session':
        if (!recipientId) {
          return new Response(JSON.stringify({ error: 'recipientId is required' }), {
            status: 400,
            headers: corsHeaders,
          });
        }
        if (recipientId === authenticatedUserId) {
          return new Response(JSON.stringify({ error: 'recipientId must be different from userId' }), {
            status: 400,
            headers: corsHeaders,
          });
        }
        {
          const profileCheck = await ensureProfilesExist(supabase, [authenticatedUserId, recipientId]);
          if (profileCheck) return profileCheck;
        }
        return handleResetSession(supabase, authenticatedUserId, recipientId, corsHeaders);
      case 'cleanup_conversation':
        if (!conversationId) {
          return new Response(JSON.stringify({ error: 'conversationId is required' }), {
            status: 400,
            headers: corsHeaders,
          });
        }
        return handleCleanupConversation(supabase, authenticatedUserId, conversationId, corsHeaders);
      case 'verify_session':
        if (!recipientId) {
          return new Response(JSON.stringify({ error: 'recipientId is required' }), {
            status: 400,
            headers: corsHeaders,
          });
        }
        if (recipientId === authenticatedUserId) {
          return new Response(JSON.stringify({ error: 'recipientId must be different from userId' }), {
            status: 400,
            headers: corsHeaders,
          });
        }
        {
          const profileCheck = await ensureProfilesExist(supabase, [authenticatedUserId, recipientId]);
          if (profileCheck) return profileCheck;
        }
        return handleVerifySession(supabase, authenticatedUserId, recipientId, corsHeaders);
      case 'force_key_refresh':
        return handleForceKeyRefresh(supabase, authenticatedUserId, corsHeaders);
      case 'sync_session_state':
        if (!recipientId) {
          return new Response(JSON.stringify({ error: 'recipientId is required' }), {
            status: 400,
            headers: corsHeaders,
          });
        }
        if (recipientId === authenticatedUserId) {
          return new Response(JSON.stringify({ error: 'recipientId must be different from userId' }), {
            status: 400,
            headers: corsHeaders,
          });
        }
        if (typeof hasSession !== 'boolean') {
          return new Response(JSON.stringify({ error: 'hasSession must be a boolean' }), {
            status: 400,
            headers: corsHeaders,
          });
        }
        {
          const profileCheck = await ensureProfilesExist(supabase, [authenticatedUserId, recipientId]);
          if (profileCheck) return profileCheck;
        }
        return handleSyncSessionState(supabase, authenticatedUserId, recipientId, hasSession, corsHeaders);
      case 'get_session_state':
        if (!recipientId) {
          return new Response(JSON.stringify({ error: 'recipientId is required' }), {
            status: 400,
            headers: corsHeaders,
          });
        }
        if (recipientId === authenticatedUserId) {
          return new Response(JSON.stringify({ error: 'recipientId must be different from userId' }), {
            status: 400,
            headers: corsHeaders,
          });
        }
        {
          const profileCheck = await ensureProfilesExist(supabase, [authenticatedUserId, recipientId]);
          if (profileCheck) return profileCheck;
        }
        return handleGetSessionState(supabase, authenticatedUserId, recipientId, corsHeaders);
      default:
        return new Response(JSON.stringify({ error: 'Invalid action' }), {
          status: 400,
          headers: corsHeaders,
        });
    }
  } catch (error) {
    console.error('[E2EE Session Manager] Error:', error);
    return new Response(JSON.stringify({ error: error.message }), {
      status: 500,
      headers: corsHeaders,
    });
  }
});

async function ensureProfilesExist(
  supabase: any,
  userIds: string[]
): Promise<Response | null> {
  const uniqueIds = [...new Set(userIds)].filter(Boolean);
  if (uniqueIds.length === 0) {
    return new Response(JSON.stringify({ error: 'Invalid user IDs' }), {
      status: 400,
      headers: corsHeaders,
    });
  }

  const { data, error } = await supabase
    .from('profiles')
    .select('id')
    .in('id', uniqueIds);

  if (error) {
    console.error('[E2EE Session Manager] Failed to verify profiles:', error);
    return new Response(JSON.stringify({ error: 'Failed to verify profiles' }), {
      status: 500,
      headers: corsHeaders,
    });
  }

  const found = new Set((data ?? []).map((row: { id: string }) => row.id));
  const missing = uniqueIds.filter((id) => !found.has(id));
  if (missing.length > 0) {
    return new Response(JSON.stringify({ error: 'Profile not found', missingIds: missing }), {
      status: 404,
      headers: corsHeaders,
    });
  }

  return null;
}

/**
 * Reset session between two users
 * - Clears session keys for both parties
 * - Forces re-establishment of encryption session
 */
async function handleResetSession(supabase: any, userId: string, recipientId: string, headers: Record<string, string>) {
  try {
    console.log(`[E2EE Session Manager] Resetting session between ${userId} and ${recipientId}`);

    // Note: We can't directly clear flutter_secure_storage from the server,
    // but we can send commands that the client will process

    // Store a session reset command in the database
    const { error } = await supabase
      .from('e2ee_session_commands')
      .insert({
        user_id: userId,
        recipient_id: recipientId,
        command_type: 'session_reset',
        status: 'pending',
        created_at: new Date().toISOString(),
      });

    if (error) {
      console.error('[E2EE Session Manager] Failed to store session reset command:', error);
      return new Response(JSON.stringify({ success: false, error: error.message }), {
        status: 500,
        headers,
      });
    }

    // Send a realtime notification to both parties
    // The client will pick this up and clear their local session keys
    await supabase
      .from('e2ee_session_events')
      .insert({
        user_id: userId,
        event_type: 'session_reset',
        recipient_id: recipientId,
        timestamp: new Date().toISOString(),
      });

    await supabase
      .from('e2ee_session_events')
      .insert({
        user_id: recipientId,
        event_type: 'session_reset',
        recipient_id: userId,
        timestamp: new Date().toISOString(),
      });

    return new Response(JSON.stringify({
      success: true,
      message: 'Session reset initiated for both parties'
    }), {
      status: 200,
      headers,
    });
  } catch (error) {
    console.error('[E2EE Session Manager] Session reset failed:', error);
    return new Response(JSON.stringify({ success: false, error: error.message }), {
      status: 500,
      headers,
    });
  }
}

/**
 * Clean up conversation and related data
 * - Deletes messages (if allowed by RLS)
 * - Clears session keys
 * - Handles cleanup of related metadata
 */
async function handleCleanupConversation(supabase: any, userId: string, conversationId: string, headers: Record<string, string>) {
  try {
    console.log(`[E2EE Session Manager] Cleaning up conversation ${conversationId} for ${userId}`);

    // Get conversation details
    const { data: conversation, error: convError } = await supabase
      .from('encrypted_conversations')
      .select('participant_a, participant_b')
      .eq('id', conversationId)
      .single();

    if (convError || !conversation) {
      console.error('[E2EE Session Manager] Conversation not found:', convError);
      return new Response(JSON.stringify({ success: false, error: 'Conversation not found' }), {
        status: 404,
        headers,
      });
    }

    // Determine the other participant
    const otherUserId = conversation.participant_a === userId
      ? conversation.participant_b
      : conversation.participant_a;

    // Delete messages sent by the current user
    const { error: deleteError } = await supabase
      .from('encrypted_messages')
      .delete()
      .eq('conversation_id', conversationId)
      .eq('sender_id', userId);

    if (deleteError) {
      console.warn('[E2EE Session Manager] Could not delete all messages:', deleteError);
      // This is acceptable - RLS might prevent deletion of some messages
    }

    // Store cleanup command for both parties
    await supabase
      .from('e2ee_session_commands')
      .insert({
        user_id: userId,
        conversation_id: conversationId,
        command_type: 'conversation_cleanup',
        status: 'pending',
        created_at: new Date().toISOString(),
      });

    await supabase
      .from('e2ee_session_commands')
      .insert({
        user_id: otherUserId,
        conversation_id: conversationId,
        command_type: 'conversation_cleanup',
        status: 'pending',
        created_at: new Date().toISOString(),
      });

    // Send realtime notifications
    await supabase
      .from('e2ee_session_events')
      .insert({
        user_id: userId,
        event_type: 'conversation_cleanup',
        conversation_id: conversationId,
        timestamp: new Date().toISOString(),
      });

    await supabase
      .from('e2ee_session_events')
      .insert({
        user_id: otherUserId,
        event_type: 'conversation_cleanup',
        conversation_id: conversationId,
        timestamp: new Date().toISOString(),
      });

    return new Response(JSON.stringify({
      success: true,
      message: 'Conversation cleanup initiated',
      otherUserId: otherUserId
    }), {
      status: 200,
      headers,
    });
  } catch (error) {
    console.error('[E2EE Session Manager] Conversation cleanup failed:', error);
    return new Response(JSON.stringify({ success: false, error: error.message }), {
      status: 500,
      headers,
    });
  }
}

/**
 * Verify if a session exists between two users
 * - Checks for existing session metadata
 * - Returns session status without exposing keys
 */
async function handleVerifySession(supabase: any, userId: string, recipientId: string, headers: Record<string, string>) {
  try {
    console.log(`[E2EE Session Manager] Verifying session between ${userId} and ${recipientId}`);

    // Check if both users have signal keys (indicates they can establish sessions)
    const { data: userKeys, error: userError } = await supabase
      .from('signal_keys')
      .select('user_id')
      .eq('user_id', userId)
      .single();

    const { data: recipientKeys, error: recipientError } = await supabase
      .from('signal_keys')
      .select('user_id')
      .eq('user_id', recipientId)
      .single();

    if (userError || !userKeys) {
      return new Response(JSON.stringify({
        success: false,
        error: 'User has no encryption keys',
        userHasKeys: false,
        recipientHasKeys: !!recipientKeys
      }), {
        status: 400,
        headers,
      });
    }

    if (recipientError || !recipientKeys) {
      return new Response(JSON.stringify({
        success: false,
        error: 'Recipient has no encryption keys',
        userHasKeys: true,
        recipientHasKeys: false
      }), {
        status: 400,
        headers,
      });
    }

    // Check if there's an existing conversation (indicates potential session)
    const { data: conversation, error: convError } = await supabase
      .from('encrypted_conversations')
      .select('id, last_message_at')
      .or(`participant_a.eq.${userId},participant_b.eq.${userId}`)
      .or(`participant_a.eq.${recipientId},participant_b.eq.${recipientId}`)
      .single();

    return new Response(JSON.stringify({
      success: true,
      userHasKeys: true,
      recipientHasKeys: true,
      hasConversation: !!conversation,
      conversationId: conversation?.id,
      lastMessageAt: conversation?.last_message_at
    }), {
      status: 200,
      headers,
    });
  } catch (error) {
    console.error('[E2EE Session Manager] Session verification failed:', error);
    return new Response(JSON.stringify({ success: false, error: error.message }), {
      status: 500,
      headers,
    });
  }
}

/**
 * Force key refresh for a user
 * - Triggers rotation of encryption keys
 * - Handles key upload and cleanup
 */
async function handleForceKeyRefresh(supabase: any, userId: string, headers: Record<string, string>) {
  try {
    console.log(`[E2EE Session Manager] Forcing key refresh for ${userId}`);

    // Store a key refresh command
    const { error } = await supabase
      .from('e2ee_session_commands')
      .insert({
        user_id: userId,
        command_type: 'key_refresh',
        status: 'pending',
        created_at: new Date().toISOString(),
      });

    if (error) {
      console.error('[E2EE Session Manager] Failed to store key refresh command:', error);
      return new Response(JSON.stringify({ success: false, error: error.message }), {
        status: 500,
        headers,
      });
    }

    // Send realtime notification
    await supabase
      .from('e2ee_session_events')
      .insert({
        user_id: userId,
        event_type: 'key_refresh',
        timestamp: new Date().toISOString(),
      });

    return new Response(JSON.stringify({
      success: true,
      message: 'Key refresh initiated',
      note: 'Client should generate new keys and upload them'
    }), {
      status: 200,
      headers,
    });
  } catch (error) {
    console.error('[E2EE Session Manager] Key refresh failed:', error);
    return new Response(JSON.stringify({ success: false, error: error.message }), {
      status: 500,
      headers,
    });
  }
}

/**
 * Sync session state between two users
 * - Updates the server-side session state tracking
 * - Detects mismatches and triggers recovery if needed
 */
async function handleSyncSessionState(
  supabase: any,
  userId: string,
  recipientId: string,
  hasSession: boolean,
  headers: Record<string, string>
) {
  try {
    console.log(`[E2EE Session Manager] Syncing session state: ${userId} -> ${recipientId}, hasSession: ${hasSession}`);

    if (!recipientId) {
      return new Response(JSON.stringify({ error: 'recipientId is required' }), {
        status: 400,
        headers,
      });
    }

    // Call the database function to update session state
    const { data, error } = await supabase.rpc('update_e2ee_session_state', {
      p_user_id: userId,
      p_peer_id: recipientId,
      p_has_session: hasSession,
    });

    if (error) {
      console.error('[E2EE Session Manager] Failed to update session state:', error);
      return new Response(JSON.stringify({ success: false, error: error.message }), {
        status: 500,
        headers,
      });
    }

    const result = data as {
      success: boolean;
      user_has_session: boolean;
      peer_has_session: boolean;
      session_mismatch: boolean;
      peer_session_version: number;
    };

    // If there's a mismatch, notify both parties
    if (result.session_mismatch) {
      console.log(`[E2EE Session Manager] Session mismatch detected between ${userId} and ${recipientId}`);

      // Insert session_mismatch event for both users
      await supabase.from('e2ee_session_events').insert([
        {
          user_id: userId,
          event_type: 'session_mismatch',
          recipient_id: recipientId,
          error_details: {
            user_has_session: result.user_has_session,
            peer_has_session: result.peer_has_session,
          },
          timestamp: new Date().toISOString(),
        },
        {
          user_id: recipientId,
          event_type: 'session_mismatch',
          recipient_id: userId,
          error_details: {
            user_has_session: result.peer_has_session,
            peer_has_session: result.user_has_session,
          },
          timestamp: new Date().toISOString(),
        },
      ]);
    }

    return new Response(JSON.stringify({
      success: true,
      userHasSession: result.user_has_session,
      peerHasSession: result.peer_has_session,
      sessionMismatch: result.session_mismatch,
      peerSessionVersion: result.peer_session_version,
    }), {
      status: 200,
      headers,
    });
  } catch (error) {
    console.error('[E2EE Session Manager] Sync session state failed:', error);
    return new Response(JSON.stringify({ success: false, error: error.message }), {
      status: 500,
      headers,
    });
  }
}

/**
 * Get current session state between two users
 * - Returns session state without modifying it
 * - Used to check for mismatches before sending messages
 */
async function handleGetSessionState(
  supabase: any,
  userId: string,
  recipientId: string,
  headers: Record<string, string>
) {
  try {
    console.log(`[E2EE Session Manager] Getting session state: ${userId} <-> ${recipientId}`);

    if (!recipientId) {
      return new Response(JSON.stringify({ error: 'recipientId is required' }), {
        status: 400,
        headers,
      });
    }

    // Call the database function to get session state
    const { data, error } = await supabase.rpc('get_e2ee_session_state', {
      p_user_id: userId,
      p_peer_id: recipientId,
    });

    if (error) {
      console.error('[E2EE Session Manager] Failed to get session state:', error);
      return new Response(JSON.stringify({ success: false, error: error.message }), {
        status: 500,
        headers,
      });
    }

    const result = data as {
      exists: boolean;
      user_has_session: boolean;
      peer_has_session: boolean;
      session_mismatch: boolean;
      user_session_version?: number;
      peer_session_version?: number;
    };

    return new Response(JSON.stringify({
      success: true,
      exists: result.exists,
      userHasSession: result.user_has_session,
      peerHasSession: result.peer_has_session,
      sessionMismatch: result.session_mismatch,
      userSessionVersion: result.user_session_version,
      peerSessionVersion: result.peer_session_version,
    }), {
      status: 200,
      headers,
    });
  } catch (error) {
    console.error('[E2EE Session Manager] Get session state failed:', error);
    return new Response(JSON.stringify({ success: false, error: error.message }), {
      status: 500,
      headers,
    });
  }
}
