import type { IncomingMessage, ServerResponse } from 'http';

import type {
  InboundMessage,
  OutboundMessage,
  ChannelAdapter,
  ChannelStatus,
} from '../types.js';
import { shouldIgnoreForMissingMention, splitMessage } from '../utils.js';
import { readRequestBody } from '../../utils/httpBody.js';

export interface FeishuAdapterConfig {
  appId: string;
  appSecret: string;
  webhookToken?: string;
  allowedChatIds?: string[];
  requireMention?: boolean;
  mentionName?: string;
  endpoint?: string;
}

interface FeishuTenantTokenResponse {
  code?: number;
  tenant_access_token?: string;
  expire?: number;
  msg?: string;
}

interface FeishuMessageSendResponse {
  code?: number;
  msg?: string;
}

interface FeishuEventEnvelope {
  type?: string;
  challenge?: string;
  header?: {
    event_type?: string;
    token?: string;
  };
  event?: {
    sender?: {
      sender_id?: {
        open_id?: string;
        user_id?: string;
      };
      sender_type?: string;
    };
    message?: {
      message_id?: string;
      chat_id?: string;
      chat_type?: string;
      message_type?: string;
      content?: string;
    };
  };
}

const MAX_MESSAGE_LENGTH = 3500;

export class FeishuAdapter implements ChannelAdapter {
  readonly name = 'feishu';
  private _status: ChannelStatus = 'disconnected';
  private messageHandler?: (msg: InboundMessage) => void;
  private tokenCache: { token: string; expiresAt: number } | null = null;

  constructor(private readonly config: FeishuAdapterConfig) {}

  get status(): ChannelStatus {
    return this._status;
  }

  onMessage(handler: (msg: InboundMessage) => void): void {
    this.messageHandler = handler;
  }

  async connect(): Promise<void> {
    this._status = 'connected';
  }

  async disconnect(): Promise<void> {
    this._status = 'disconnected';
  }

  async send(peerId: string, message: OutboundMessage): Promise<void> {
    if (this._status !== 'connected') {
      throw new Error('Feishu adapter not connected');
    }

    const text = message.text.trim();
    if (!text) {
      return;
    }

    const chunks = text.length > MAX_MESSAGE_LENGTH ? splitMessage(text, MAX_MESSAGE_LENGTH) : [text];
    for (const chunk of chunks) {
      await this.sendMessage(peerId, chunk);
    }
  }

  async handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
    let body = '';
    try {
      body = await readRequestBody(req, { maxBytes: 1_048_576 });
    } catch {
      res.writeHead(400, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Invalid request body' }));
      return;
    }

    let payload: FeishuEventEnvelope;
    try {
      payload = JSON.parse(body) as FeishuEventEnvelope;
    } catch {
      res.writeHead(400, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Invalid JSON' }));
      return;
    }

    // URL verification flow
    if (payload.type === 'url_verification' && payload.challenge) {
      res.writeHead(200, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ challenge: payload.challenge }));
      return;
    }

    if (this.config.webhookToken && payload.header?.token !== this.config.webhookToken) {
      res.writeHead(401, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Invalid webhook token' }));
      return;
    }

    await this.handleEvent(payload);
    res.writeHead(202, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ accepted: true }));
  }

  async handleEvent(payload: FeishuEventEnvelope): Promise<void> {
    if (!this.messageHandler) {
      return;
    }
    if (payload.header?.event_type !== 'im.message.receive_v1') {
      return;
    }

    const message = payload.event?.message;
    if (!message || message.message_type !== 'text') {
      return;
    }
    const chatId = message.chat_id?.trim();
    if (!chatId) {
      return;
    }
    if (this.config.allowedChatIds && this.config.allowedChatIds.length > 0) {
      if (!this.config.allowedChatIds.includes(chatId)) {
        return;
      }
    }

    const senderId = payload.event?.sender?.sender_id?.open_id?.trim()
      || payload.event?.sender?.sender_id?.user_id?.trim();
    if (!senderId) {
      return;
    }

    const text = parseFeishuText(message.content);
    if (!text) {
      return;
    }

    const mentionName = this.config.mentionName ?? 'flynn';
    const mentionRegex = new RegExp(`(?:^|\\s)@?${escapeRegex(mentionName)}(?:\\b|:)`, 'i');
    const isDm = (message.chat_type ?? '').toLowerCase() === 'p2p';
    const mentionsBot = mentionRegex.test(text);
    if (shouldIgnoreForMissingMention({
      requireMention: this.config.requireMention,
      defaultRequireMention: true,
      mentionsBot: isDm || mentionsBot,
    })) {
      return;
    }

    const cleaned = text.replace(new RegExp(`^\\s*@?${escapeRegex(mentionName)}(?:\\b|:)\\s*`, 'i'), '').trim();
    if (!cleaned) {
      return;
    }

    this.messageHandler({
      id: message.message_id ?? `feishu-${Date.now()}`,
      channel: 'feishu',
      senderId,
      text: cleaned,
      timestamp: Date.now(),
      metadata: {
        chatId,
        chatType: message.chat_type,
        replyPeerId: chatId,
      },
    });
  }

  private async sendMessage(chatId: string, text: string): Promise<void> {
    const token = await this.getTenantAccessToken();
    const endpoint = `${this.baseEndpoint()}/open-apis/im/v1/messages?receive_id_type=chat_id`;
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json; charset=utf-8',
      },
      body: JSON.stringify({
        receive_id: chatId,
        msg_type: 'text',
        content: JSON.stringify({ text }),
      }),
    });
    const payload = await response.json().catch(() => ({})) as FeishuMessageSendResponse;
    if (!response.ok || payload.code !== 0) {
      throw new Error(`Feishu send failed (${response.status}): ${payload.msg ?? 'unknown error'}`);
    }
  }

  private async getTenantAccessToken(): Promise<string> {
    const now = Date.now();
    if (this.tokenCache && this.tokenCache.expiresAt > now + 30_000) {
      return this.tokenCache.token;
    }

    const endpoint = `${this.baseEndpoint()}/open-apis/auth/v3/tenant_access_token/internal`;
    const response = await fetch(endpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json; charset=utf-8' },
      body: JSON.stringify({
        app_id: this.config.appId,
        app_secret: this.config.appSecret,
      }),
    });
    const payload = await response.json().catch(() => ({})) as FeishuTenantTokenResponse;
    if (!response.ok || payload.code !== 0 || !payload.tenant_access_token) {
      throw new Error(`Feishu auth failed (${response.status}): ${payload.msg ?? 'missing token'}`);
    }
    const expireSecs = typeof payload.expire === 'number' ? payload.expire : 3600;
    this.tokenCache = {
      token: payload.tenant_access_token,
      expiresAt: now + expireSecs * 1000,
    };
    return payload.tenant_access_token;
  }

  private baseEndpoint(): string {
    return (this.config.endpoint ?? 'https://open.feishu.cn').replace(/\/+$/, '');
  }
}

function parseFeishuText(content: string | undefined): string {
  if (!content) {
    return '';
  }
  try {
    const parsed = JSON.parse(content) as { text?: string };
    return (parsed.text ?? '').trim();
  } catch {
    return '';
  }
}

function escapeRegex(value: string): string {
  return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
