import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import {
  BaseConnector,
  ConnectionTestResult,
} from '../base-connector';
import { IntegrationConfigError } from '../../errors';
import { CredentialsService } from '../../credentials/credentials.service';
import {
  ZulipAuthConfig,
  ZulipMessage,
  ZulipSendMessageRequest,
  ZulipSendMessageResponse,
  ZulipGetMessagesRequest,
  ZulipGetMessagesResponse,
  ZulipStream,
  ZulipStreamSubscription,
  ZulipGetStreamsResponse,
  ZulipGetSubscriptionsResponse,
  ZulipSubscribeRequest,
  ZulipSubscribeResponse,
  ZulipUser,
  ZulipGetUsersResponse,
  ZulipNarrow,
} from './zulip.types';

/**
 * ZULIP API Connector
 *
 * Provides integration with ZULIP team chat platform.
 * Uses Basic Authentication with email and API key.
 * Credentials are loaded lazily from the DB via CredentialsService.
 *
 * @see https://zulip.com/api/
 */
@Injectable()
export class ZulipConnector extends BaseConnector {
  protected readonly name = 'ZULIP';
  private authConfig: ZulipAuthConfig = { baseUrl: '', email: '', apiKey: '' };
  private initialized = false;
  private configuredState = false;

  constructor(private readonly credentialsService: CredentialsService) {
    super();
  }

  /**
   * Load credentials from DB and configure the connector.
   * Idempotent - returns immediately if already initialized.
   */
  async ensureInitialized(): Promise<void> {
    if (this.initialized) return;

    const result = await this.credentialsService.findActiveByType('ZULIP');

    if (!result) {
      this.configuredState = false;
      this.initialized = true;
      return;
    }

    const { baseUrl, email, apiKey } = result.credentials;

    this.authConfig = {
      baseUrl: baseUrl || '',
      email: email || '',
      apiKey: apiKey || '',
    };

    if (this.authConfig.baseUrl) {
      this.reconfigure({
        baseUrl: `${this.authConfig.baseUrl}/api/v1`,
        timeout: 30000,
        maxRetries: 3,
      });
    }

    this.configuredState = !!(
      this.authConfig.baseUrl &&
      this.authConfig.email &&
      this.authConfig.apiKey
    );
    this.initialized = true;
  }

  /**
   * Reload credentials from DB (called when credentials change)
   */
  async reload(): Promise<void> {
    this.initialized = false;
    await this.ensureInitialized();
  }

  /**
   * Handle credential change events
   */
  @OnEvent('credentials.changed')
  async onCredentialsChanged(payload: { type: string }): Promise<void> {
    if (payload.type === 'ZULIP') {
      this.logger.log('ZULIP credentials changed, reloading');
      await this.reload();
    }
  }

  /**
   * Check if the connector is properly configured (last known state)
   */
  isConfigured(): boolean {
    return this.configuredState;
  }

  /**
   * Get missing configuration items
   */
  getMissingConfig(): string[] {
    const missing: string[] = [];
    if (!this.authConfig.baseUrl) missing.push('baseUrl');
    if (!this.authConfig.email) missing.push('email');
    if (!this.authConfig.apiKey) missing.push('apiKey');
    return missing;
  }

  /**
   * Test the connection to ZULIP
   */
  async testConnection(): Promise<ConnectionTestResult> {
    await this.ensureInitialized();

    if (!this.isConfigured()) {
      return {
        success: false,
        message: `Missing configuration: ${this.getMissingConfig().join(', ')}`,
      };
    }

    const startTime = Date.now();

    try {
      const response = await this.get<{
        result: string;
        zulip_version: string;
        zulip_feature_level: number;
      }>('/server_settings');

      if (response.result !== 'success') {
        throw new Error('Server returned non-success result');
      }

      await this.get<{ result: string }>('/users/me');

      return {
        success: true,
        message: 'Successfully connected to ZULIP',
        latencyMs: Date.now() - startTime,
        details: {
          baseUrl: this.authConfig.baseUrl,
          zulipVersion: response.zulip_version,
          featureLevel: response.zulip_feature_level,
        },
      };
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error';
      return {
        success: false,
        message: `Connection failed: ${message}`,
        latencyMs: Date.now() - startTime,
      };
    }
  }

  /**
   * Get authentication headers for requests (Basic Auth)
   */
  async getAuthHeaders(): Promise<Record<string, string>> {
    await this.ensureInitialized();

    if (!this.isConfigured()) {
      throw new IntegrationConfigError(this.name, this.getMissingConfig());
    }

    const credentials = Buffer.from(
      `${this.authConfig.email}:${this.authConfig.apiKey}`,
    ).toString('base64');

    return {
      Authorization: `Basic ${credentials}`,
    };
  }

  // ===========================================================================
  // Messages API
  // ===========================================================================

  /**
   * Get messages
   */
  async getMessages(
    request: ZulipGetMessagesRequest = {},
  ): Promise<ZulipGetMessagesResponse> {
    await this.ensureInitialized();
    const params = this.buildMessagesParams(request);
    return this.get<ZulipGetMessagesResponse>('/messages', { params });
  }

  /**
   * Get messages from a specific stream
   */
  async getStreamMessages(
    streamName: string,
    topic?: string,
    numMessages = 50,
  ): Promise<ZulipMessage[]> {
    const narrow: ZulipNarrow[] = [
      { operator: 'stream', operand: streamName },
    ];

    if (topic) {
      narrow.push({ operator: 'topic', operand: topic });
    }

    const response = await this.getMessages({
      anchor: 'newest',
      num_before: numMessages,
      num_after: 0,
      narrow,
    });

    return response.messages;
  }

  /**
   * Get private messages with a user
   */
  async getPrivateMessages(
    userEmail: string,
    numMessages = 50,
  ): Promise<ZulipMessage[]> {
    const response = await this.getMessages({
      anchor: 'newest',
      num_before: numMessages,
      num_after: 0,
      narrow: [
        { operator: 'pm-with', operand: userEmail },
      ],
    });

    return response.messages;
  }

  /**
   * Search messages
   */
  async searchMessages(
    searchTerm: string,
    options: {
      stream?: string;
      limit?: number;
    } = {},
  ): Promise<ZulipMessage[]> {
    const narrow: ZulipNarrow[] = [
      { operator: 'search', operand: searchTerm },
    ];

    if (options.stream) {
      narrow.push({ operator: 'stream', operand: options.stream });
    }

    const response = await this.getMessages({
      anchor: 'newest',
      num_before: options.limit || 50,
      num_after: 0,
      narrow,
    });

    return response.messages;
  }

  /**
   * Send a message
   */
  async sendMessage(
    request: ZulipSendMessageRequest,
  ): Promise<ZulipSendMessageResponse> {
    await this.ensureInitialized();
    const formData = new URLSearchParams();
    formData.append('type', request.type);

    if (Array.isArray(request.to)) {
      formData.append('to', JSON.stringify(request.to));
    } else {
      formData.append('to', request.to);
    }

    if (request.topic) {
      formData.append('topic', request.topic);
    }

    formData.append('content', request.content);

    if (request.queue_id) {
      formData.append('queue_id', request.queue_id);
    }

    if (request.local_id) {
      formData.append('local_id', request.local_id);
    }

    return this.post<ZulipSendMessageResponse>('/messages', formData.toString(), {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
    });
  }

  /**
   * Send a message to a stream
   */
  async sendStreamMessage(
    stream: string,
    topic: string,
    content: string,
  ): Promise<ZulipSendMessageResponse> {
    return this.sendMessage({
      type: 'stream',
      to: stream,
      topic,
      content,
    });
  }

  /**
   * Send a private message
   */
  async sendPrivateMessage(
    userIds: number[],
    content: string,
  ): Promise<ZulipSendMessageResponse> {
    return this.sendMessage({
      type: 'private',
      to: userIds,
      content,
    });
  }

  // ===========================================================================
  // Streams API
  // ===========================================================================

  /**
   * Get all streams
   */
  async getStreams(
    options: {
      includePublic?: boolean;
      includeSubscribed?: boolean;
      includeAllActive?: boolean;
      includeDefault?: boolean;
      includeOwnerSubscribed?: boolean;
    } = {},
  ): Promise<ZulipStream[]> {
    await this.ensureInitialized();
    const params: Record<string, string> = {};

    if (options.includePublic !== undefined) {
      params.include_public = String(options.includePublic);
    }
    if (options.includeSubscribed !== undefined) {
      params.include_subscribed = String(options.includeSubscribed);
    }
    if (options.includeAllActive !== undefined) {
      params.include_all_active = String(options.includeAllActive);
    }
    if (options.includeDefault !== undefined) {
      params.include_default = String(options.includeDefault);
    }
    if (options.includeOwnerSubscribed !== undefined) {
      params.include_owner_subscribed = String(options.includeOwnerSubscribed);
    }

    const response = await this.get<ZulipGetStreamsResponse>('/streams', {
      params,
    });

    return response.streams;
  }

  /**
   * Get subscribed streams
   */
  async getSubscriptions(): Promise<ZulipStreamSubscription[]> {
    await this.ensureInitialized();
    const response = await this.get<ZulipGetSubscriptionsResponse>(
      '/users/me/subscriptions',
    );
    return response.subscriptions;
  }

  /**
   * Subscribe to streams
   */
  async subscribe(
    request: ZulipSubscribeRequest,
  ): Promise<ZulipSubscribeResponse> {
    await this.ensureInitialized();
    const formData = new URLSearchParams();
    formData.append('subscriptions', JSON.stringify(request.subscriptions));

    if (request.principals) {
      formData.append('principals', JSON.stringify(request.principals));
    }

    if (request.invite_only !== undefined) {
      formData.append('invite_only', String(request.invite_only));
    }

    if (request.history_public_to_subscribers !== undefined) {
      formData.append(
        'history_public_to_subscribers',
        String(request.history_public_to_subscribers),
      );
    }

    if (request.announce !== undefined) {
      formData.append('announce', String(request.announce));
    }

    return this.post<ZulipSubscribeResponse>(
      '/users/me/subscriptions',
      formData.toString(),
      {
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
      },
    );
  }

  /**
   * Unsubscribe from streams
   */
  async unsubscribe(streamNames: string[]): Promise<{ result: string }> {
    await this.ensureInitialized();
    const formData = new URLSearchParams();
    formData.append('subscriptions', JSON.stringify(streamNames));

    return this.delete<{ result: string }>(
      `/users/me/subscriptions?${formData.toString()}`,
    );
  }

  // ===========================================================================
  // Users API
  // ===========================================================================

  /**
   * Get all users
   */
  async getUsers(
    options: {
      clientGravatar?: boolean;
      includeCustomProfileFields?: boolean;
    } = {},
  ): Promise<ZulipUser[]> {
    await this.ensureInitialized();
    const params: Record<string, string> = {};

    if (options.clientGravatar !== undefined) {
      params.client_gravatar = String(options.clientGravatar);
    }

    if (options.includeCustomProfileFields !== undefined) {
      params.include_custom_profile_fields = String(
        options.includeCustomProfileFields,
      );
    }

    const response = await this.get<ZulipGetUsersResponse>('/users', { params });
    return response.members;
  }

  /**
   * Get current user profile
   */
  async getCurrentUser(): Promise<ZulipUser> {
    await this.ensureInitialized();
    return this.get<ZulipUser>('/users/me');
  }

  /**
   * Get a specific user by ID
   */
  async getUser(userId: number): Promise<ZulipUser> {
    await this.ensureInitialized();
    const response = await this.get<{ user: ZulipUser }>(`/users/${userId}`);
    return response.user;
  }

  /**
   * Get a specific user by email
   */
  async getUserByEmail(email: string): Promise<ZulipUser> {
    await this.ensureInitialized();
    const response = await this.get<{ user: ZulipUser }>(
      `/users/${encodeURIComponent(email)}`,
    );
    return response.user;
  }

  // ===========================================================================
  // Helper Methods
  // ===========================================================================

  /**
   * Build query parameters for messages endpoint
   */
  private buildMessagesParams(
    request: ZulipGetMessagesRequest,
  ): Record<string, string> {
    const params: Record<string, string> = {};

    if (request.anchor !== undefined) {
      params.anchor = String(request.anchor);
    }

    if (request.include_anchor !== undefined) {
      params.include_anchor = String(request.include_anchor);
    }

    if (request.num_before !== undefined) {
      params.num_before = String(request.num_before);
    }

    if (request.num_after !== undefined) {
      params.num_after = String(request.num_after);
    }

    if (request.narrow) {
      params.narrow = JSON.stringify(request.narrow);
    }

    if (request.apply_markdown !== undefined) {
      params.apply_markdown = String(request.apply_markdown);
    }

    if (request.use_first_unread_anchor !== undefined) {
      params.use_first_unread_anchor = String(request.use_first_unread_anchor);
    }

    return params;
  }
}
