import { Injectable, Logger } from '@nestjs/common';
import { ZulipConnector } from './zulip.connector';
import {
  GetMessagesQueryDto,
  SendStreamMessageDto,
  SendPrivateMessageDto,
  GetStreamsQueryDto,
  SubscribeRequestDto,
  GetUsersQueryDto,
  MessageResponseDto,
  MessagesListResponseDto,
  SendMessageResponseDto,
  StreamResponseDto,
  StreamSubscriptionResponseDto,
  UserResponseDto,
} from './dto';
import { ConnectionTestResult } from '../base-connector';
import { ZulipMessage, ZulipStream, ZulipStreamSubscription, ZulipUser } from './zulip.types';

/**
 * Service layer for ZULIP integration
 *
 * Provides business logic and data transformation for ZULIP API calls.
 */
@Injectable()
export class ZulipService {
  private readonly logger = new Logger(ZulipService.name);

  constructor(private readonly connector: ZulipConnector) {}

  /**
   * Test the connection to ZULIP
   */
  async testConnection(): Promise<ConnectionTestResult> {
    this.logger.log('Testing ZULIP connection');
    return this.connector.testConnection();
  }

  /**
   * Check if ZULIP integration is configured
   */
  isConfigured(): boolean {
    return this.connector.isConfigured();
  }

  // ===========================================================================
  // Messages
  // ===========================================================================

  /**
   * Get messages with optional filters
   */
  async getMessages(query: GetMessagesQueryDto): Promise<MessagesListResponseDto> {
    this.logger.log(`Fetching messages with query: ${JSON.stringify(query)}`);

    let messages: ZulipMessage[];

    if (query.search) {
      messages = await this.connector.searchMessages(query.search, {
        stream: query.stream,
        limit: query.numBefore,
      });
    } else if (query.stream) {
      messages = await this.connector.getStreamMessages(
        query.stream,
        query.topic,
        query.numBefore,
      );
    } else {
      const response = await this.connector.getMessages({
        anchor: this.parseAnchor(query.anchor),
        num_before: query.numBefore,
        num_after: query.numAfter,
      });
      messages = response.messages;

      return {
        messages: messages.map(this.mapMessageToDto),
        foundOldest: response.found_oldest,
        foundNewest: response.found_newest,
        anchor: response.anchor,
      };
    }

    return {
      messages: messages.map(this.mapMessageToDto),
      foundOldest: true,
      foundNewest: true,
      anchor: messages.length > 0 ? messages[0].id : 0,
    };
  }

  /**
   * Send a message to a stream
   */
  async sendStreamMessage(
    dto: SendStreamMessageDto,
  ): Promise<SendMessageResponseDto> {
    this.logger.log(`Sending stream message to ${dto.stream}/${dto.topic}`);

    const response = await this.connector.sendStreamMessage(
      dto.stream,
      dto.topic,
      dto.content,
    );

    return {
      id: response.id,
      result: response.result,
      message: response.msg,
    };
  }

  /**
   * Send a private message
   */
  async sendPrivateMessage(
    dto: SendPrivateMessageDto,
  ): Promise<SendMessageResponseDto> {
    this.logger.log(`Sending private message to users: ${dto.userIds.join(', ')}`);

    const response = await this.connector.sendPrivateMessage(
      dto.userIds,
      dto.content,
    );

    return {
      id: response.id,
      result: response.result,
      message: response.msg,
    };
  }

  // ===========================================================================
  // Streams
  // ===========================================================================

  /**
   * Get list of streams
   */
  async getStreams(query: GetStreamsQueryDto): Promise<StreamResponseDto[]> {
    this.logger.log('Fetching streams');

    const streams = await this.connector.getStreams({
      includePublic: query.includePublic,
      includeSubscribed: query.includeSubscribed,
    });

    return streams.map(this.mapStreamToDto);
  }

  /**
   * Get user's subscribed streams
   */
  async getSubscriptions(): Promise<StreamSubscriptionResponseDto[]> {
    this.logger.log('Fetching subscriptions');

    const subscriptions = await this.connector.getSubscriptions();

    return subscriptions.map(this.mapSubscriptionToDto);
  }

  /**
   * Subscribe to streams
   */
  async subscribe(dto: SubscribeRequestDto): Promise<{
    subscribed: string[];
    alreadySubscribed: string[];
  }> {
    this.logger.log(`Subscribing to streams: ${dto.subscriptions.map(s => s.name).join(', ')}`);

    const response = await this.connector.subscribe({
      subscriptions: dto.subscriptions,
      invite_only: dto.inviteOnly,
    });

    // Extract stream names from the response
    const subscribed = Object.keys(response.subscribed);
    const alreadySubscribed = Object.keys(response.already_subscribed);

    return {
      subscribed,
      alreadySubscribed,
    };
  }

  /**
   * Unsubscribe from streams
   */
  async unsubscribe(streamNames: string[]): Promise<{ success: boolean }> {
    this.logger.log(`Unsubscribing from streams: ${streamNames.join(', ')}`);

    const response = await this.connector.unsubscribe(streamNames);

    return {
      success: response.result === 'success',
    };
  }

  // ===========================================================================
  // Users
  // ===========================================================================

  /**
   * Get list of users
   */
  async getUsers(query: GetUsersQueryDto): Promise<UserResponseDto[]> {
    this.logger.log('Fetching users');

    const users = await this.connector.getUsers({
      includeCustomProfileFields: query.includeCustomProfileFields,
    });

    return users.map(this.mapUserToDto);
  }

  /**
   * Get current authenticated user
   */
  async getCurrentUser(): Promise<UserResponseDto> {
    this.logger.log('Fetching current user');

    const user = await this.connector.getCurrentUser();

    return this.mapUserToDto(user);
  }

  // ===========================================================================
  // Mappers
  // ===========================================================================

  /**
   * Parse anchor value (can be number or special string)
   */
  private parseAnchor(
    anchor?: string,
  ): number | 'newest' | 'oldest' | 'first_unread' | undefined {
    if (!anchor) return 'newest';

    if (['newest', 'oldest', 'first_unread'].includes(anchor)) {
      return anchor as 'newest' | 'oldest' | 'first_unread';
    }

    const parsed = parseInt(anchor, 10);
    return isNaN(parsed) ? 'newest' : parsed;
  }

  /**
   * Map Zulip message to response DTO
   */
  private mapMessageToDto = (message: ZulipMessage): MessageResponseDto => {
    const streamName =
      typeof message.display_recipient === 'string'
        ? message.display_recipient
        : undefined;

    return {
      id: message.id,
      senderId: message.sender_id,
      senderEmail: message.sender_email,
      senderFullName: message.sender_full_name,
      content: message.content,
      type: message.type,
      streamId: message.stream_id,
      streamName,
      subject: message.subject,
      timestamp: message.timestamp,
      avatarUrl: message.avatar_url,
    };
  };

  /**
   * Map Zulip stream to response DTO
   */
  private mapStreamToDto = (stream: ZulipStream): StreamResponseDto => ({
    streamId: stream.stream_id,
    name: stream.name,
    description: stream.description,
    inviteOnly: stream.invite_only,
    isWebPublic: stream.is_web_public,
    dateCreated: stream.date_created,
  });

  /**
   * Map Zulip subscription to response DTO
   */
  private mapSubscriptionToDto = (
    subscription: ZulipStreamSubscription,
  ): StreamSubscriptionResponseDto => ({
    streamId: subscription.stream_id,
    name: subscription.name,
    description: subscription.description,
    inviteOnly: subscription.invite_only,
    isWebPublic: subscription.is_web_public,
    dateCreated: subscription.date_created,
    color: subscription.color,
    isMuted: subscription.is_muted,
    pinToTop: subscription.pin_to_top,
    emailAddress: subscription.email_address,
  });

  /**
   * Map Zulip user to response DTO
   */
  private mapUserToDto = (user: ZulipUser): UserResponseDto => ({
    userId: user.user_id,
    email: user.email,
    fullName: user.full_name,
    avatarUrl: user.avatar_url,
    isAdmin: user.is_admin,
    isBot: user.is_bot,
    isActive: user.is_active,
    timezone: user.timezone,
    dateJoined: user.date_joined,
  });
}
