import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import SockJS from 'sockjs-client';
import { Client, IMessage, StompSubscription } from '@stomp/stompjs';
import axios from 'axios';
import { AuthService } from './auth.service';
import { UserService } from './user.service';
import { LoggerService } from './logger.service';

import { BASE_URL, WS_URL, getWsUrl } from '../config/api.config';

export interface Message {
  id?: string;
  senderId: string;
  senderName: string;
  receiverId: string;
  receiverName: string;
  content: string;
  isRead: boolean;
  createdAt: string;
}

export interface Conversation {
  otherUserId: string;
  otherUserName: string;
  otherUserRole: string;
  isOnline: boolean;
  otherUserStatus?: 'ONLINE' | 'OFFLINE' | 'BUSY';
  lastSeen?: string;
  unreadCount: number;
  lastMessage?: Message;
  messages?: Message[];
  otherUserAvatarUrl?: string;
}

export interface ChatUser {
  userId: string;
  firstName: string;
  lastName: string;
  specialization?: string;
  isVerified?: boolean;
  isOnline: boolean;
  status?: 'ONLINE' | 'OFFLINE' | 'BUSY';
  avatarUrl?: string;
}

@Injectable({
  providedIn: 'root'
})
export class ChatService {
  private stompClient: Client | null = null;
  private connected = false;
  private messageSubject = new Subject<Message>();
  private conversationsSubject = new BehaviorSubject<Conversation[]>([]);
  private onlineStatusSubject = new Subject<{ userId: string; isOnline: boolean; status?: string; lastSeen?: string }>();
  private typingSubject = new Subject<{ userId: string; isTyping: boolean; senderName?: string }>();

  public messages$ = this.messageSubject.asObservable();
  public conversations$ = this.conversationsSubject.asObservable();
  public onlineStatus$ = this.onlineStatusSubject.asObservable();
  public typing$ = this.typingSubject.asObservable();
  private currentUserId: string | null = null;

  constructor(
    private authService: AuthService,
    private userService: UserService,
    private logger: LoggerService
  ) {}

  async connect(): Promise<void> {
    if (this.connected || !this.authService.isAuthenticated()) {
      return;
    }

    const token = this.authService.getToken();
    if (!token) {
      this.logger.error('No token available for WebSocket connection');
      return;
    }

    // Get current user ID and status
    let currentUserStatus: 'ONLINE' | 'OFFLINE' | 'BUSY' | null = null;
    try {
      const user = await this.userService.getCurrentUser();
      if (user) {
        this.currentUserId = user.id;
        currentUserStatus = (user.status as 'ONLINE' | 'OFFLINE' | 'BUSY') || null;
      }
    } catch (error) {
      this.logger.error('Error getting current user:', error);
    }

    // Use dynamic WebSocket URL to ensure it's computed at runtime
    const wsUrl = getWsUrl();
    this.logger.debug('[ChatService] Connecting to WebSocket:', wsUrl);
    this.stompClient = new Client({
      webSocketFactory: () => new SockJS(wsUrl) as any,
      connectHeaders: {
        Authorization: `Bearer ${token}`
      },
      reconnectDelay: 5000,
      heartbeatIncoming: 4000,
      heartbeatOutgoing: 4000,
      onConnect: () => {
        this.logger.debug('WebSocket Connected');
        this.connected = true;
        this.subscribeToMessages();
        // Backend now handles respecting OFFLINE/BUSY status automatically
        // We just need to notify backend that WebSocket is connected
        // Backend will check current status and only update if appropriate
        setTimeout(() => {
          this.updateOnlineStatus(true).catch(err => {
            this.logger.warn('Failed to update online status on connect:', err);
          });
        }, 500);
      },
      onStompError: (frame) => {
        this.logger.error('WebSocket STOMP error:', frame);
      },
      onWebSocketClose: () => {
        this.logger.debug('WebSocket Disconnected');
        this.connected = false;
        // Don't update online status on disconnect if token still exists
        // This preserves status during page refresh - it will be restored on reconnect
        // Only explicit disconnect() call (logout) will set status to OFFLINE
        // The backend updateOnlineStatus(false) now only sets isOnline=false without changing status
      }
    });

    this.stompClient.activate();
  }

  async disconnect(): Promise<void> {
    if (this.stompClient) {
      // Update online status to false before disconnecting (explicit logout)
      // Only try if we have a valid token (avoid 401 on logout)
      const token = this.authService.getToken();
      if (token && this.connected) {
        try {
          // Call updateUserStatus with OFFLINE to explicitly set status to OFFLINE on logout
          await axios.put(`${BASE_URL}/api/v3/messages/status?status=OFFLINE`, {}, {
            headers: { Authorization: `Bearer ${token}` }
          });
        } catch (err: any) {
          // Silently ignore 401 errors during logout (token may already be invalid)
          // Don't log as error since this is expected behavior during logout
          if (err?.response?.status !== 401) {
            this.logger.warn('Could not update status to OFFLINE on disconnect:', err);
          }
        }
      }
      this.stompClient.deactivate();
      this.connected = false;
    }
  }

  private subscribeToMessages(): void {
    if (!this.stompClient || !this.connected) return;

    const userId = this.currentUserId || this.getUserIdFromToken(this.authService.getToken()!);

    if (userId) {
      // Subscribe to personal message queue - using email as identifier for user-specific queue
      const userEmail = this.userService.getUserEmail();
      const queueIdentifier = userEmail || userId;
      
      this.stompClient.subscribe(`/user/queue/messages`, (message: IMessage) => {
        const msg: Message = JSON.parse(message.body);
        this.messageSubject.next(msg);
        this.refreshConversations();
      });

      // Subscribe to typing notifications
      this.stompClient.subscribe(`/user/queue/typing`, (message: IMessage) => {
        try {
          const typingData = JSON.parse(message.body);
          this.logger.debug('Typing notification received:', typingData);
          this.typingSubject.next({
            userId: typingData.senderId,
            isTyping: typingData.isTyping,
            senderName: typingData.senderName
          });
        } catch (error) {
          this.logger.error('Error parsing typing notification:', error);
        }
      });
    }

    // Subscribe to online status updates
    this.stompClient.subscribe('/topic/online-status', (message: IMessage) => {
      const status = JSON.parse(message.body);
      this.onlineStatusSubject.next(status);
      // Refresh conversations when online status changes to update UI
      this.refreshConversations();
    });
  }

  sendMessage(receiverId: string, content: string): void {
    if (!this.stompClient || !this.connected) {
      this.logger.error('WebSocket not connected');
      return;
    }

    const message: any = {
      receiverId,
      content
    };

    this.stompClient.publish({
      destination: '/app/chat.send',
      body: JSON.stringify(message)
    });
  }

  sendTypingNotification(receiverId: string, isTyping: boolean): void {
    if (!this.stompClient || !this.connected) {
      this.logger.warn('WebSocket not connected, cannot send typing notification');
      return;
    }

    try {
      this.stompClient.publish({
        destination: '/app/chat.typing',
        body: JSON.stringify({
          receiverId,
          isTyping
        })
      });
    } catch (error) {
      this.logger.error('Error sending typing notification:', error);
    }
  }

  async getConversations(): Promise<Conversation[]> {
    try {
      const token = this.authService.getToken();
      const res = await axios.get(`${BASE_URL}/api/v3/messages/conversations`, {
        headers: { Authorization: `Bearer ${token}` }
      });
      const conversations = res.data;
      
      // Deduplicate conversations by otherUserId
      const uniqueConversations = conversations.reduce((acc: Conversation[], conversation: Conversation) => {
        const existingIndex = acc.findIndex(c => c.otherUserId === conversation.otherUserId);
        if (existingIndex === -1) {
          acc.push(conversation);
        } else {
          // Keep the most recent conversation
          const existing = acc[existingIndex];
          if (conversation.lastMessage && (!existing.lastMessage || 
              new Date(conversation.lastMessage.createdAt) > new Date(existing.lastMessage.createdAt))) {
            acc[existingIndex] = conversation;
          } else {
            // Update status if duplicate has newer status
            existing.otherUserStatus = conversation.otherUserStatus || existing.otherUserStatus;
            existing.isOnline = conversation.isOnline;
            existing.lastSeen = conversation.lastSeen;
          }
        }
        return acc;
      }, [] as Conversation[]);
      
      this.conversationsSubject.next(uniqueConversations);
      return uniqueConversations;
    } catch (error) {
      this.logger.error('Error fetching conversations:', error);
      return [];
    }
  }

  async getConversation(otherUserId: string): Promise<Message[]> {
    try {
      const token = this.authService.getToken();
      const response = await axios.get(`${BASE_URL}/api/v3/messages/conversation/${otherUserId}`, {
        headers: { Authorization: `Bearer ${token}` }
      });
      return response.data;
    } catch (error) {
      this.logger.error('Error fetching conversation:', error);
      return [];
    }
  }

  async markAsRead(senderId: string): Promise<void> {
    try {
      const token = this.authService.getToken();
      await axios.put(`${BASE_URL}/api/v3/messages/read/${senderId}`, {}, {
        headers: { Authorization: `Bearer ${token}` }
      });
      this.refreshConversations();
    } catch (error) {
      this.logger.error('Error marking messages as read:', error);
    }
  }

  async getUnreadCount(): Promise<number> {
    try {
      const token = this.authService.getToken();
      const response = await axios.get(`${BASE_URL}/api/v3/messages/unread/count`, {
        headers: { Authorization: `Bearer ${token}` }
      });
      return response.data;
    } catch (error) {
      this.logger.error('Error fetching unread count:', error);
      return 0;
    }
  }

  async refreshConversations(): Promise<void> {
    await this.getConversations();
  }

  async updateUserStatus(status: 'ONLINE' | 'OFFLINE' | 'BUSY'): Promise<void> {
    try {
      const token = this.authService.getToken();
      await axios.put(`${BASE_URL}/api/v3/messages/status?status=${status}`, {}, {
        headers: { Authorization: `Bearer ${token}` }
      });
    } catch (error) {
      this.logger.error('Error updating user status:', error);
      throw error;
    }
  }

  private async updateOnlineStatus(online: boolean): Promise<void> {
    try {
      const token = this.authService.getToken();
      if (!token) {
        // No token available, skip update (likely during logout)
        return;
      }
      await axios.put(`${BASE_URL}/api/v3/messages/status/online?online=${online}`, {}, {
        headers: { Authorization: `Bearer ${token}` }
      });
    } catch (error: any) {
      // Don't log 401 errors as they're expected during logout
      if (error?.response?.status !== 401) {
        this.logger.error('Error updating online status:', error);
      }
    }
  }

  // Expose a safe public method to update online status on lifecycle events
  async setOnline(online: boolean): Promise<void> {
    await this.updateOnlineStatus(online);
  }

  private getUserIdFromToken(token: string): string | null {
    try {
      const payload = JSON.parse(atob(token.split('.')[1]));
      return payload.sub || payload.userId || null;
    } catch {
      return null;
    }
  }

  isConnected(): boolean {
    return this.connected;
  }

  getCurrentUserId(): string | null {
    return this.currentUserId;
  }

  async searchDoctors(query: string = ''): Promise<ChatUser[]> {
    try {
      const token = this.authService.getToken();
      if (!token) {
        this.logger.error('No authentication token available');
        return [];
      }
      const response = await axios.get(`${BASE_URL}/api/v3/messages/search/doctors`, {
        params: { query },
        headers: { Authorization: `Bearer ${token}` }
      });
      this.logger.debug('Search doctors response:', response.data);
      return response.data || [];
    } catch (error: any) {
      this.logger.error('Error searching doctors:', error);
      if (error.response) {
        this.logger.error('Response status:', error.response.status);
        this.logger.error('Response data:', error.response.data);
      }
      return [];
    }
  }

  async searchPatients(query: string = ''): Promise<ChatUser[]> {
    try {
      const token = this.authService.getToken();
      if (!token) {
        this.logger.error('No authentication token available');
        return [];
      }
      const response = await axios.get(`${BASE_URL}/api/v3/messages/search/patients`, {
        params: { query },
        headers: { Authorization: `Bearer ${token}` }
      });
      this.logger.debug('Search patients response:', response.data);
      return response.data || [];
    } catch (error: any) {
      this.logger.error('Error searching patients:', error);
      if (error.response) {
        this.logger.error('Response status:', error.response.status);
        this.logger.error('Response data:', error.response.data);
      }
      return [];
    }
  }

  async deleteMessage(messageId: string): Promise<void> {
    try {
      const token = this.authService.getToken();
      if (!token) {
        throw new Error('No authentication token available');
      }
      await axios.delete(`${BASE_URL}/api/v3/messages/message/${messageId}`, {
        headers: { Authorization: `Bearer ${token}` }
      });
      // Refresh conversations after deletion
      await this.refreshConversations();
    } catch (error: any) {
      this.logger.error('Error deleting message:', error);
      throw error;
    }
  }

  async deleteConversation(otherUserId: string): Promise<void> {
    try {
      const token = this.authService.getToken();
      if (!token) {
        throw new Error('No authentication token available');
      }
      await axios.delete(`${BASE_URL}/api/v3/messages/conversation/${otherUserId}`, {
        headers: { Authorization: `Bearer ${token}` }
      });
      // Refresh conversations after deletion
      await this.refreshConversations();
    } catch (error: any) {
      this.logger.error('Error deleting conversation:', error);
      throw error;
    }
  }

  async blockUser(userId: string): Promise<void> {
    try {
      const token = this.authService.getToken();
      if (!token) {
        throw new Error('No authentication token available');
      }
      await axios.post(`${BASE_URL}/api/v3/messages/block/${userId}`, {}, {
        headers: { Authorization: `Bearer ${token}` }
      });
      // Refresh conversations after blocking
      await this.refreshConversations();
    } catch (error: any) {
      this.logger.error('Error blocking user:', error);
      throw error;
    }
  }

  async unblockUser(userId: string): Promise<void> {
    try {
      const token = this.authService.getToken();
      if (!token) {
        throw new Error('No authentication token available');
      }
      // Ensure userId is properly formatted as a string and remove any whitespace
      let formattedUserId = String(userId).trim();
      
      // Validate UUID format (basic check for UUID structure)
      const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
      if (!uuidRegex.test(formattedUserId)) {
        this.logger.error('Invalid UUID format:', formattedUserId);
        throw new Error(`Invalid user ID format: ${formattedUserId}`);
      }
      
      this.logger.debug('Unblocking user with ID:', formattedUserId);
      const url = `${BASE_URL}/api/v3/messages/block/${formattedUserId}`;
      this.logger.debug('Unblock URL:', url);
      
      const response = await axios.delete(url, {
        headers: { Authorization: `Bearer ${token}` }
      });
      this.logger.debug('Unblock response:', response.data);
      // Refresh conversations after unblocking
      await this.refreshConversations();
    } catch (error: any) {
      this.logger.error('Error unblocking user:', error, {
        response: error?.response?.data,
        status: error?.response?.status,
        message: error?.message,
        userId: userId
      });
      
      // Re-throw with more details
      const errorMessage = error?.response?.data?.message || error?.response?.data?.error || error?.message || 'Failed to unblock user';
      const enhancedError = new Error(errorMessage);
      (enhancedError as any).response = error?.response;
      throw enhancedError;
    }
  }

  async isBlocked(userId: string): Promise<boolean> {
    try {
      const token = this.authService.getToken();
      if (!token) {
        return false;
      }
      const response = await axios.get(`${BASE_URL}/api/v3/messages/block/status/${userId}`, {
        headers: { Authorization: `Bearer ${token}` }
      });
      return response.data.blocked || false;
    } catch (error: any) {
      this.logger.error('Error checking block status:', error);
      return false;
    }
  }

  async getBlockedUsers(): Promise<string[]> {
    try {
      const token = this.authService.getToken();
      if (!token) {
        return [];
      }
      const response = await axios.get(`${BASE_URL}/api/v3/messages/block/list`, {
        headers: { Authorization: `Bearer ${token}` }
      });
      return response.data || [];
    } catch (error: any) {
      this.logger.error('Error getting blocked users:', error);
      return [];
    }
  }

  async getBlockedUsersWithDetails(): Promise<ChatUser[]> {
    try {
      const token = this.authService.getToken();
      if (!token) {
        this.logger.warn('No authentication token available for getting blocked users');
        return [];
      }
      const response = await axios.get(`${BASE_URL}/api/v3/messages/block/list/details`, {
        headers: { Authorization: `Bearer ${token}` }
      });
      this.logger.debug('Blocked users with details response:', response.data);
      
      // Ensure userId is converted to string if it comes as UUID
      const users: ChatUser[] = (response.data || []).map((user: any) => {
        // Handle UUID - it might come as an object with a toString() method or as a string
        let userIdStr: string;
        if (user.userId) {
          if (typeof user.userId === 'string') {
            userIdStr = user.userId;
          } else if (user.userId.toString) {
            userIdStr = user.userId.toString();
          } else {
            userIdStr = String(user.userId);
          }
          // Ensure it's a valid UUID format
          userIdStr = userIdStr.trim();
          this.logger.debug('Processing blocked user:', user.firstName, user.lastName, 'userId:', userIdStr);
        } else {
          this.logger.warn('Blocked user missing userId:', user);
          userIdStr = '';
        }
        
        return {
          ...user,
          userId: userIdStr
        };
      });
      
      return users;
    } catch (error: any) {
      this.logger.error('Error getting blocked users with details:', error);
      if (error.response) {
        this.logger.error('Response status:', error.response.status);
        this.logger.error('Response data:', error.response.data);
      }
      return [];
    }
  }

  async startConversationWithUser(userId: string): Promise<Conversation | null> {
    // Load conversations to see if one already exists
    await this.refreshConversations();
    const existing = (await this.getConversations()).find(c => c.otherUserId === userId);
    
    if (existing) {
      return existing;
    }
    
    // Create a new conversation entry (no messages yet)
    const user = await this.getUserInfo(userId);
    if (!user) return null;

    return {
      otherUserId: userId,
      otherUserName: `${user.firstName} ${user.lastName}`,
      otherUserRole: '',
      isOnline: user.isOnline || false,
      unreadCount: 0,
      messages: []
    };
  }

  private async getUserInfo(userId: string): Promise<ChatUser | null> {
    // Try to get from search results based on current user's role
    try {
      if (this.currentUserId) {
        const user = await this.userService.getCurrentUser();
        if (user?.role === 'PATIENT') {
          const doctors = await this.searchDoctors('');
          return doctors.find(u => u.userId === userId) || null;
        } else if (user?.role === 'DOCTOR') {
          const patients = await this.searchPatients('');
          return patients.find(u => u.userId === userId) || null;
        }
      }
    } catch (error) {
      this.logger.error('Error getting user info:', error);
    }
    return null;
  }
}

