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 BlueBubblesAdapterConfig {
  endpoint: string;
  apiKey: string;
  webhookToken?: string;
  allowedChatGuids?: string[];
  requireMention?: boolean;
  mentionName?: string;
}

interface BlueBubblesWebhookEvent {
  token?: string;
  message?: {
    guid?: string;
    text?: string;
    chatGuid?: string;
    isFromMe?: boolean;
    sender?: { displayName?: string };
  };
  data?: {
    guid?: string;
    text?: string;
    chatGuid?: string;
    isFromMe?: boolean;
    sender?: { displayName?: string };
  };
  event?: string;
}

const MAX_MESSAGE_LENGTH = 3500;

export class BlueBubblesAdapter implements ChannelAdapter {
  readonly name = 'bluebubbles';
  private _status: ChannelStatus = 'disconnected';
  private messageHandler?: (msg: InboundMessage) => void;

  constructor(private readonly config: BlueBubblesAdapterConfig) {}

  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('BlueBubbles 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.sendText(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: BlueBubblesWebhookEvent;
    try {
      payload = JSON.parse(body) as BlueBubblesWebhookEvent;
    } catch {
      res.writeHead(400, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Invalid JSON' }));
      return;
    }

    if (this.config.webhookToken) {
      const headerToken = req.headers['x-bluebubbles-token'];
      const token = typeof headerToken === 'string' ? headerToken : payload.token;
      if (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: BlueBubblesWebhookEvent): Promise<void> {
    if (!this.messageHandler) {
      return;
    }
    const message = payload.message ?? payload.data;
    if (!message) {
      return;
    }
    if (message.isFromMe) {
      return;
    }

    const chatGuid = message.chatGuid?.trim();
    if (!chatGuid) {
      return;
    }
    if (this.config.allowedChatGuids && this.config.allowedChatGuids.length > 0) {
      if (!this.config.allowedChatGuids.includes(chatGuid)) {
        return;
      }
    }

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

    const mentionName = this.config.mentionName ?? 'flynn';
    const mentionsBot = new RegExp(`(?:^|\\s)@?${escapeRegex(mentionName)}(?:\\b|:)`, 'i').test(text);
    const isGroup = chatGuid.startsWith('chat');
    if (shouldIgnoreForMissingMention({
      requireMention: this.config.requireMention,
      defaultRequireMention: true,
      mentionsBot: !isGroup || mentionsBot,
    })) {
      return;
    }

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

    this.messageHandler({
      id: message.guid ?? `bluebubbles-${Date.now()}`,
      channel: 'bluebubbles',
      senderId: chatGuid,
      senderName: message.sender?.displayName,
      text: cleaned,
      timestamp: Date.now(),
      metadata: {
        chatGuid,
        event: payload.event,
        replyPeerId: chatGuid,
      },
    });
  }

  private async sendText(chatGuid: string, text: string): Promise<void> {
    const endpoint = this.config.endpoint.replace(/\/+$/, '');
    const response = await fetch(`${endpoint}/api/v1/message/text`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${this.config.apiKey}`,
      },
      body: JSON.stringify({
        chatGuid,
        message: text,
      }),
    });

    if (!response.ok) {
      const responseBody = await response.text().catch(() => '');
      throw new Error(`BlueBubbles send failed (${response.status}): ${responseBody}`);
    }
  }
}

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