const db = require('../database/db');
const crypto = require('crypto');

class CallService {

  /**
   * Initiate a call
   */
  async initiateCall(initiatorId, conversationId, type, participantUserIds = []) {
    // Validate type
    if (!['voice', 'video'].includes(type)) {
      throw new Error('Invalid call type');
    }

    // Verify initiator is in conversation
    const participantCheck = await db.query(
      `SELECT * FROM conversation_participants 
       WHERE conversation_id = $1 AND user_id = $2`,
      [conversationId, initiatorId]
    );

    if (participantCheck.rows.length === 0) {
      throw new Error('User is not a participant in this conversation');
    }

    // Get conversation details
    const conversationResult = await db.query(
      `SELECT * FROM conversations WHERE id = $1`,
      [conversationId]
    );

    if (conversationResult.rows.length === 0) {
      throw new Error(`Conversation ${conversationId} not found`);
    }

    const conversation = conversationResult.rows[0];

    // Determine if group call
    const isGroupCall = participantUserIds.length > 1 || conversation.type === 'group';

    // Create call record
    const callResult = await db.query(
      `INSERT INTO calls 
       (conversation_id, type, initiator_id, status)
       VALUES ($1, $2, $3, 'ringing')
       RETURNING *`,
      [conversationId, type, initiatorId]
    );

    const call = callResult.rows[0];

    // If group call, create SFU room
    let sfuRoomId = null;
    if (isGroupCall) {
      sfuRoomId = await this.createSFURoom(call.id);
      await db.query(
        `UPDATE calls SET sfu_room_id = $2 WHERE id = $1`,
        [call.id, sfuRoomId]
      );
    }

    // Add participants
    let targetParticipants;
    if (participantUserIds.length > 0) {
      targetParticipants = participantUserIds;
    } else {
      // Get other participants from conversation
      const participantsResult = await db.query(
        `SELECT user_id FROM conversation_participants 
         WHERE conversation_id = $1 AND user_id != $2`,
        [conversationId, initiatorId]
      );
      targetParticipants = participantsResult.rows.map(r => r.user_id);
    }

    // Add initiator
    await db.query(
      `INSERT INTO call_participants (call_id, user_id, joined_at)
       VALUES ($1, $2, NOW())`,
      [call.id, initiatorId]
    );

    // Add target participants with ringing status
    for (const userId of targetParticipants) {
      await db.query(
        `INSERT INTO call_participants (call_id, user_id)
         VALUES ($1, $2)`,
        [call.id, userId]
      );
    }

    // Generate TURN credentials
    const turnCredentials = await this.generateTURNCredentials(initiatorId);

    return {
      id: call.id,
      conversationId: conversationId,
      type: type,
      status: 'ringing',
      initiatorId: initiatorId,
      isGroupCall: isGroupCall,
      sfuRoomId: sfuRoomId,
      participants: targetParticipants.map(userId => ({
        userId: userId,
        status: 'ringing'
      })),
      turnCredentials: turnCredentials,
      createdAt: call.created_at
    };
  }

  /**
   * Answer a call
   */
  async answerCall(callId, userId, accept) {
    // Get call
    const callResult = await db.query(
      `SELECT * FROM calls WHERE id = $1`,
      [callId]
    );

    if (callResult.rows.length === 0) {
      throw new Error('Call not found');
    }

    const call = callResult.rows[0];

    if (!accept) {
      // Reject call
      await this.endCall(callId, userId, 'rejected');
      return {
        id: callId,
        status: 'ended',
        endReason: 'rejected'
      };
    }

    // Accept call
    await db.query(
      `UPDATE call_participants 
       SET joined_at = NOW() 
       WHERE call_id = $1 AND user_id = $2`,
      [callId, userId]
    );

    // If this is a 1-on-1 call, mark as active
    if (!call.sfu_room_id) {
      await db.query(
        `UPDATE calls 
         SET status = 'active', started_at = NOW() 
         WHERE id = $1`,
        [callId]
      );
    }

    // Generate TURN credentials
    const turnCredentials = await this.generateTURNCredentials(userId);

    const response = {
      id: callId,
      status: 'active',
      turnCredentials: turnCredentials
    };

    // If group call, include SFU config
    if (call.sfu_room_id) {
      response.sfuConfig = await this.getSFUConfig(call.sfu_room_id);
    }

    return response;
  }

  /**
   * End a call
   */
  async endCall(callId, userId, reason = 'hangup') {
    // Get call
    const callResult = await db.query(
      `SELECT * FROM calls WHERE id = $1`,
      [callId]
    );

    if (callResult.rows.length === 0) {
      throw new Error('Call not found');
    }

    const call = callResult.rows[0];

    // Calculate duration
    let duration = null;
    if (call.started_at) {
      const now = new Date();
      const started = new Date(call.started_at);
      duration = Math.floor((now - started) / 1000); // seconds
    }

    // Update call
    await db.query(
      `UPDATE calls 
       SET status = 'ended', ended_at = NOW(), duration_seconds = $2
       WHERE id = $1`,
      [callId, duration]
    );

    // Update participant
    await db.query(
      `UPDATE call_participants 
       SET left_at = NOW() 
       WHERE call_id = $1 AND user_id = $2`,
      [callId, userId]
    );

    // If group call, check if should close SFU room
    if (call.sfu_room_id) {
      const remainingParticipants = await db.query(
        `SELECT COUNT(*) as count 
         FROM call_participants 
         WHERE call_id = $1 AND left_at IS NULL`,
        [callId]
      );

      if (parseInt(remainingParticipants.rows[0].count) === 0) {
        await this.closeSFURoom(call.sfu_room_id);
      }
    }

    return {
      id: callId,
      status: 'ended',
      duration: duration,
      endedBy: userId,
      reason: reason
    };
  }

  /**
   * Update media state for participant
   */
  async updateMediaState(callId, userId, mediaState) {
    await db.query(
      `UPDATE call_participants 
       SET media_state = $3 
       WHERE call_id = $1 AND user_id = $2`,
      [callId, userId, JSON.stringify(mediaState)]
    );

    return mediaState;
  }

  /**
   * Generate TURN credentials (temporary, time-limited)
   */
  async generateTURNCredentials(userId) {
    const timestamp = Math.floor(Date.now() / 1000) + 86400; // 24 hour TTL
    const username = `${timestamp}:${userId}`;

    // Generate credential using HMAC
    const turnSecret = process.env.TURN_SECRET;
    if (!turnSecret) {
      throw new Error('TURN_SECRET environment variable must be set for TURN server authentication');
    }
    const hmac = crypto.createHmac('sha1', turnSecret);
    hmac.update(username);
    const credential = hmac.digest('base64');

    // Store in database
    await db.query(
      `INSERT INTO turn_credentials (user_id, username, credential, expires_at)
       VALUES ($1, $2, $3, to_timestamp($4))
       ON CONFLICT (user_id) 
       DO UPDATE SET username = $2, credential = $3, expires_at = to_timestamp($4)`,
      [userId, username, credential, timestamp]
    );

    const turnHost = process.env.TURN_SERVER_HOST;
    const turnPort = process.env.TURN_SERVER_PORT;

    if (!turnHost || !turnPort) {
      throw new Error('TURN_SERVER_HOST and TURN_SERVER_PORT environment variables must be set');
    }

    return {
      urls: [
        `stun:${turnHost}:${turnPort}`,
        `turn:${turnHost}:${turnPort}`,
        `turn:${turnHost}:${turnPort}?transport=tcp`
      ],
      username: username,
      credential: credential,
      ttl: 86400
    };
  }

  /**
   * Create SFU room for group call (using Mediasoup)
   */
  async createSFURoom(callId) {
    // This would integrate with Mediasoup
    // For now, return placeholder
    const roomId = `room-${callId}`;
    
    // In production, this would:
    // 1. Create a Mediasoup Router
    // 2. Configure codecs and RTP capabilities
    // 3. Store router reference
    
    console.log(`Created SFU room: ${roomId}`);
    return roomId;
  }

  /**
   * Get SFU configuration for client
   */
  async getSFUConfig(roomId) {
    // This would return Mediasoup router capabilities
    // Placeholder for now
    return {
      routerRtpCapabilities: {
        codecs: [
          {
            kind: 'audio',
            mimeType: 'audio/opus',
            clockRate: 48000,
            channels: 2
          },
          {
            kind: 'video',
            mimeType: 'video/VP8',
            clockRate: 90000
          },
          {
            kind: 'video',
            mimeType: 'video/VP9',
            clockRate: 90000
          }
        ]
      },
      roomId: roomId
    };
  }

  /**
   * Close SFU room
   */
  async closeSFURoom(roomId) {
    // Close Mediasoup router
    // Cleanup resources
    console.log(`Closed SFU room: ${roomId}`);
  }

  /**
   * Get call details
   */
  async getCall(callId) {
    const callResult = await db.query(
      `SELECT c.*, 
         u.username as initiator_name,
         i.identifier as initiator_identifier
       FROM calls c
       LEFT JOIN users u ON c.initiator_id = u.id
       LEFT JOIN identities i ON u.id = i.user_id AND i.is_primary = true
       WHERE c.id = $1`,
      [callId]
    );

    if (callResult.rows.length === 0) {
      throw new Error('Call not found');
    }

    const call = callResult.rows[0];

    // Get participants
    const participantsResult = await db.query(
      `SELECT cp.*, u.username, i.identifier
       FROM call_participants cp
       LEFT JOIN users u ON cp.user_id = u.id
       LEFT JOIN identities i ON u.id = i.user_id AND i.is_primary = true
       WHERE cp.call_id = $1`,
      [callId]
    );

    return {
      ...call,
      participants: participantsResult.rows
    };
  }
}

module.exports = new CallService();
