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 TeamsAdapterConfig {
  appId: string;
  appPassword: string;
  allowedConversationIds?: string[];
  requireMention?: boolean;
}

interface BotTokenCache {
  token: string;
  expiresAt: number;
}

interface TeamsActivity {
  type?: string;
  id?: string;
  text?: string;
  serviceUrl?: string;
  conversation?: { id?: string; conversationType?: string };
  from?: { id?: string; name?: string };
  recipient?: { id?: string; name?: string };
  entities?: Array<{ type?: string; mentioned?: { id?: string; name?: string } }>;
  timestamp?: string;
}

const MAX_MESSAGE_LENGTH = 2500;

export class TeamsAdapter implements ChannelAdapter {
  readonly name = 'teams';

  private _status: ChannelStatus = 'disconnected';
  private messageHandler?: (msg: InboundMessage) => void;
  private tokenCache: BotTokenCache | null = null;

  constructor(private readonly config: TeamsAdapterConfig) {}

  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('Teams adapter not connected');
    }

    const ref = this.decodePeerRef(peerId);
    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.sendActivity(ref.serviceUrl, ref.conversationId, 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 activity: TeamsActivity;
    try {
      activity = JSON.parse(body) as TeamsActivity;
    } catch {
      res.writeHead(400, { 'Content-Type': 'application/json' });
      res.end(JSON.stringify({ error: 'Invalid JSON' }));
      return;
    }

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

  async handleActivity(activity: TeamsActivity): Promise<void> {
    if (!this.messageHandler) {
      return;
    }
    if (activity.type !== 'message') {
      return;
    }

    const text = (activity.text ?? '').trim();
    const conversationId = activity.conversation?.id?.trim();
    const serviceUrl = activity.serviceUrl?.trim();
    const fromId = activity.from?.id?.trim();
    if (!text || !conversationId || !serviceUrl || !fromId) {
      return;
    }

    if (this.config.allowedConversationIds && this.config.allowedConversationIds.length > 0) {
      if (!this.config.allowedConversationIds.includes(conversationId)) {
        return;
      }
    }

    const conversationType = (activity.conversation?.conversationType ?? '').toLowerCase();
    const isPersonal = conversationType === 'personal';
    const mentionsBot = this.activityMentionsBot(activity);
    if (shouldIgnoreForMissingMention({
      requireMention: this.config.requireMention,
      defaultRequireMention: true,
      mentionsBot: isPersonal || mentionsBot,
    })) {
      return;
    }

    const cleaned = this.stripLeadingMention(text).trim();
    if (!cleaned) {
      return;
    }

    const peerRef = this.encodePeerRef({ conversationId, serviceUrl });
    this.messageHandler({
      id: activity.id ?? `teams-${Date.now()}`,
      channel: 'teams',
      senderId: fromId,
      senderName: activity.from?.name,
      text: cleaned,
      timestamp: activity.timestamp ? Date.parse(activity.timestamp) || Date.now() : Date.now(),
      metadata: {
        conversationId,
        serviceUrl,
        replyPeerId: peerRef,
      },
    });
  }

  private stripLeadingMention(text: string): string {
    return text
      .replace(/^\s*<at>[^<]+<\/at>\s*/i, '')
      .replace(/\s+/g, ' ');
  }

  private activityMentionsBot(activity: TeamsActivity): boolean {
    const recipientId = activity.recipient?.id;
    if (!recipientId) {
      return false;
    }
    const mentionEntity = activity.entities?.find((entity) =>
      entity.type?.toLowerCase() === 'mention' && entity.mentioned?.id === recipientId,
    );
    return Boolean(mentionEntity);
  }

  private async sendActivity(serviceUrl: string, conversationId: string, text: string): Promise<void> {
    const token = await this.getBotToken();
    const base = serviceUrl.replace(/\/+$/, '');
    const endpoint = `${base}/v3/conversations/${encodeURIComponent(conversationId)}/activities`;

    const response = await fetch(endpoint, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        type: 'message',
        text,
      }),
    });

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

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

    const form = new URLSearchParams();
    form.set('grant_type', 'client_credentials');
    form.set('client_id', this.config.appId);
    form.set('client_secret', this.config.appPassword);
    form.set('scope', 'https://api.botframework.com/.default');

    const response = await fetch('https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: form.toString(),
    });
    if (!response.ok) {
      const body = await response.text().catch(() => '');
      throw new Error(`Teams token fetch failed (${response.status}): ${body}`);
    }

    const payload = await response.json() as { access_token?: string; expires_in?: number };
    if (!payload.access_token) {
      throw new Error('Teams token fetch failed: missing access_token');
    }
    const expiresIn = typeof payload.expires_in === 'number' ? payload.expires_in : 3600;
    this.tokenCache = {
      token: payload.access_token,
      expiresAt: now + expiresIn * 1000,
    };
    return payload.access_token;
  }

  private encodePeerRef(ref: { conversationId: string; serviceUrl: string }): string {
    const payload = Buffer.from(JSON.stringify(ref), 'utf8').toString('base64url');
    return `teams:${payload}`;
  }

  private decodePeerRef(peerId: string): { conversationId: string; serviceUrl: string } {
    if (!peerId.startsWith('teams:')) {
      throw new Error('Teams peerId must use teams:<base64url-json> format');
    }
    const encoded = peerId.slice('teams:'.length);
    let decoded = '';
    try {
      decoded = Buffer.from(encoded, 'base64url').toString('utf8');
    } catch {
      throw new Error('Invalid Teams peer reference encoding');
    }

    let parsed: { conversationId?: string; serviceUrl?: string } = {};
    try {
      parsed = JSON.parse(decoded) as { conversationId?: string; serviceUrl?: string };
    } catch {
      throw new Error('Invalid Teams peer reference payload');
    }
    if (!parsed.conversationId || !parsed.serviceUrl) {
      throw new Error('Teams peer reference missing conversationId/serviceUrl');
    }
    return {
      conversationId: parsed.conversationId,
      serviceUrl: parsed.serviceUrl,
    };
  }
}
