import { env } from '../config/env';
import { logger } from '../utils/logger';

// --- Types ---

export interface RCUser {
  _id: string;
  username: string;
  name: string;
  emails: { address: string; verified: boolean }[];
  roles: string[];
  active: boolean;
}

export interface RCLoginResponse {
  status: string;
  data: {
    userId: string;
    authToken: string;
    me: RCUser;
  };
}

export interface RCUserCreateResponse {
  user: RCUser;
  success: boolean;
}

export interface RCUserTokenResponse {
  data: {
    userId: string;
    authToken: string;
  };
  success: boolean;
}

export interface RCInfoResponse {
  info: {
    version: string;
  };
  success: boolean;
}

export interface RCChannelCreateResponse {
  channel: {
    _id: string;
    name: string;
  };
  success: boolean;
}

// --- Client ---

class RocketChatClient {
  private adminToken: string | null = null;
  private adminUserId: string | null = null;
  private tokenExpiresAt = 0;

  private get baseUrl(): string {
    return env.ROCKETCHAT_URL;
  }

  private get hasCredentials(): boolean {
    return !!env.ROCKETCHAT_ADMIN_USER &&
      !!env.ROCKETCHAT_ADMIN_PASSWORD;
  }

  /**
   * Make an authenticated request to Rocket.Chat REST API
   */
  private async request<T>(
    method: string,
    path: string,
    body?: unknown,
    skipAuth = false,
  ): Promise<T> {
    const url = `${this.baseUrl}/api/v1${path}`;
    const headers: Record<string, string> = {
      'Content-Type': 'application/json',
    };

    if (!skipAuth) {
      await this.ensureAdminAuth();
      if (this.adminToken && this.adminUserId) {
        headers['X-Auth-Token'] = this.adminToken;
        headers['X-User-Id'] = this.adminUserId;
      }
    }

    const res = await fetch(url, {
      method,
      headers,
      body: body ? JSON.stringify(body) : undefined,
    });

    const data = await res.json() as T & { success?: boolean; error?: string };

    if (!res.ok || data.success === false) {
      const msg = data.error || `RC API error ${res.status}`;
      throw new Error(msg);
    }

    return data;
  }

  // --- Auth ---

  /**
   * Authenticate as admin, cache token for 1 hour
   */
  async ensureAdminAuth(): Promise<void> {
    if (this.adminToken && Date.now() < this.tokenExpiresAt) return;

    const url = `${this.baseUrl}/api/v1/login`;
    const res = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        user: env.ROCKETCHAT_ADMIN_USER,
        password: env.ROCKETCHAT_ADMIN_PASSWORD,
      }),
    });

    const data = await res.json() as RCLoginResponse;
    if (!res.ok || data.status !== 'success') {
      throw new Error('Failed to authenticate with Rocket.Chat admin');
    }

    this.adminToken = data.data.authToken;
    this.adminUserId = data.data.userId;
    // Cache for 1 hour
    this.tokenExpiresAt = Date.now() + 60 * 60 * 1000;
    logger.debug('Rocket.Chat admin auth refreshed');
  }

  // --- Health ---

  async healthCheck(): Promise<boolean> {
    if (!this.hasCredentials) return false;
    try {
      const url = `${this.baseUrl}/api/info`;
      const res = await fetch(url, { method: 'GET', signal: AbortSignal.timeout(5000) });
      return res.ok;
    } catch {
      return false;
    }
  }

  // --- Channel History ---

  /**
   * Get recent messages from a channel by name
   */
  async getChannelHistory(channelName: string, count = 5): Promise<Array<{
    _id: string;
    msg: string;
    u: { username: string };
    ts: string;
    bot?: { i: string };
    alias?: string;
  }>> {
    try {
      const data = await this.request<{
        messages: Array<{
          _id: string;
          msg: string;
          u: { username: string };
          ts: string;
          bot?: { i: string };
          alias?: string;
        }>;
        success: boolean;
      }>('GET', `/channels.history?roomName=${encodeURIComponent(channelName)}&count=${count}`);
      return data.messages || [];
    } catch {
      return [];
    }
  }

  // --- User Management ---

  /**
   * Find a Rocket.Chat user by email address
   */
  async findUserByEmail(email: string): Promise<RCUser | null> {
    try {
      const data = await this.request<{ users: RCUser[]; success: boolean }>(
        'GET',
        `/users.list?query=${encodeURIComponent(email)}&count=1`,
      );
      const match = data.users?.find(u =>
        u.emails?.some(e => e.address.toLowerCase() === email.toLowerCase()),
      );
      return match || null;
    } catch (err) {
      logger.warn('RC findUserByEmail failed:', err);
      return null;
    }
  }

  /**
   * Create a new Rocket.Chat user
   */
  async createUser(data: {
    email: string;
    name: string;
    username: string;
    password: string;
    roles?: string[];
  }): Promise<RCUser> {
    const res = await this.request<RCUserCreateResponse>('POST', '/users.create', {
      email: data.email,
      name: data.name,
      username: data.username,
      password: data.password,
      roles: data.roles || ['user'],
      verified: true,
      joinDefaultChannels: true,
      requirePasswordChange: false,
      sendWelcomeEmail: false,
    });
    return res.user;
  }

  /**
   * Update an existing Rocket.Chat user's roles and name
   */
  async updateUser(userId: string, data: { name?: string; roles?: string[] }): Promise<void> {
    await this.request('POST', '/users.update', {
      userId,
      data,
    });
  }

  /**
   * Set user active/inactive status
   */
  async setUserActive(userId: string, active: boolean): Promise<void> {
    await this.request('POST', '/users.setActiveStatus', {
      userId,
      activeStatus: active,
    });
  }

  /**
   * Generate a one-time login token for a user (SSO)
   * Requires CREATE_TOKENS_FOR_USERS=true on the RC instance
   */
  async createUserToken(userId: string): Promise<{ authToken: string; userId: string }> {
    const res = await this.request<RCUserTokenResponse>('POST', '/users.createToken', {
      userId,
    });
    return res.data;
  }

  // --- Channels ---

  /**
   * Create a channel (idempotent — returns existing if name taken)
   */
  async ensureChannel(name: string, topic?: string): Promise<string> {
    try {
      const res = await this.request<RCChannelCreateResponse>('POST', '/channels.create', {
        name,
        readOnly: false,
      });
      if (topic) {
        await this.request('POST', '/channels.setTopic', {
          roomId: res.channel._id,
          topic,
        });
      }
      return res.channel._id;
    } catch (err) {
      // Channel already exists — look it up
      if (err instanceof Error && (err.message.includes('already in use') || err.message.includes('duplicate-channel-name') || err.message.includes('exists'))) {
        const info = await this.request<{ channel: { _id: string }; success: boolean }>(
          'GET',
          `/channels.info?roomName=${encodeURIComponent(name)}`,
        );
        return info.channel._id;
      }
      throw err;
    }
  }

  /**
   * Add a user to a public channel by channel name. Idempotent — returns the
   * channel info regardless of whether the user was already a member. Used by
   * the JSN identity bridge to make sure every JSN supporter lands in a
   * specific channel (#supporters by convention), independent of RC's
   * default-channel flag (which only fires at user creation time).
   */
  async inviteUserToChannel(userId: string, channelName: string): Promise<void> {
    await this.request('POST', '/channels.invite', { roomName: channelName, userId });
  }

  // --- Direct Messages ---

  /**
   * Create or get a DM room between two users (by username).
   * RC im.create is idempotent — returns existing room if it already exists.
   */
  async createDM(usernames: string[]): Promise<{ roomId: string; usernames: string[] }> {
    const data = await this.request<{
      room: { _id: string; usernames: string[] };
      success: boolean;
    }>('POST', '/im.create', { usernames: usernames.join(',') });
    return {
      roomId: data.room._id,
      usernames: data.room.usernames || usernames,
    };
  }

  // --- Dashboard / Stats ---

  /**
   * Get server-wide statistics
   */
  async getStatistics(): Promise<{
    totalUsers: number;
    onlineUsers: number;
    totalChannels: number;
    totalMessages: number;
  } | null> {
    try {
      const data = await this.request<{
        statistics: {
          totalUsers: number;
          onlineUsers: number;
          totalRooms: number;
          totalChannels: number;
          totalMessages: number;
        };
        success: boolean;
      }>('GET', '/statistics');
      return {
        totalUsers: data.statistics.totalUsers,
        onlineUsers: data.statistics.onlineUsers,
        totalChannels: data.statistics.totalChannels,
        totalMessages: data.statistics.totalMessages,
      };
    } catch (err) {
      logger.warn('RC getStatistics failed:', err instanceof Error ? err.message : err);
      return null;
    }
  }

  /**
   * List channels with message counts (for dashboard top channels)
   */
  async listChannels(limit = 10): Promise<Array<{
    name: string;
    msgs: number;
    usersCount: number;
  }>> {
    try {
      const data = await this.request<{
        channels: Array<{
          name: string;
          msgs: number;
          usersCount: number;
        }>;
        success: boolean;
      }>('GET', `/channels.list?count=${limit}&sort=${encodeURIComponent('{"msgs":-1}')}`);
      return data.channels || [];
    } catch (err) {
      logger.warn('RC listChannels failed:', err instanceof Error ? err.message : err);
      return [];
    }
  }

  // --- Webhooks ---

  /**
   * Post a message to an incoming webhook URL
   */
  async postWebhook(webhookUrl: string, payload: {
    text?: string;
    channel?: string;
    alias?: string;
    emoji?: string;
    attachments?: Array<{
      title?: string;
      text?: string;
      color?: string;
      fields?: Array<{ title: string; value: string; short?: boolean }>;
    }>;
  }): Promise<void> {
    try {
      await fetch(webhookUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
      });
    } catch (err) {
      logger.warn('RC webhook post failed:', err);
    }
  }

  /**
   * Post a message to a channel using the REST API (no webhook needed)
   */
  async postMessage(channel: string, text: string, alias?: string): Promise<void> {
    try {
      await this.request('POST', '/chat.postMessage', {
        channel,
        text,
        alias: alias || 'Changemaker Bot',
      });
    } catch (err) {
      logger.warn(`RC postMessage to ${channel} failed:`, err);
    }
  }
}

export const rocketchatClient = new RocketChatClient();
