/**
 * TextMe Channel Plugin - Outbound Message Handler
 * 
 * Receives outbound requests from Clawdbot and sends via TextMe API.
 * Includes webhook server for the standard channel plugin interface.
 */

import * as http from 'http';
import type { TextMeAPI, SendMessageResponse } from 'textme-unofficial-api';
import { AuthManager, type AuthenticatedAccount } from './auth.js';
import type { PluginConfig, AccountConfig } from './config.js';

// ============================================================================
// Types
// ============================================================================

export interface OutboundRequest {
  /** Target identifier (phone number or conversation ID) */
  target: string;
  /** Message content */
  message: string;
  /** Specific account ID to use */
  accountId?: string;
  /** Specific phone number to send from */
  fromNumber?: string;
  /** Attachment URLs */
  attachments?: string[];
  /** Reply to message ID */
  replyTo?: string;
  /** Additional metadata */
  metadata?: Record<string, unknown>;
}

export interface OutboundResponse {
  success: boolean;
  messageId?: string;
  error?: string;
  details?: unknown;
}

export interface ProbeResponse {
  channel: string;
  version: string;
  capabilities: string[];
  accounts: {
    id: string;
    name: string;
    phoneNumbers: string[];
    enabled: boolean;
  }[];
  status: 'ready' | 'degraded' | 'offline';
}

export interface OutboundHandlerOptions {
  /** HTTP port for webhook server */
  port: number;
  /** URL path prefix */
  pathPrefix: string;
  /** Enable debug logging */
  debug?: boolean;
}

// ============================================================================
// Outbound Handler
// ============================================================================

export class OutboundHandler {
  private authManager: AuthManager;
  private options: OutboundHandlerOptions;
  private server: http.Server | null = null;
  private accounts: AccountConfig[];

  constructor(
    authManager: AuthManager,
    accounts: AccountConfig[],
    options: OutboundHandlerOptions
  ) {
    this.authManager = authManager;
    this.accounts = accounts;
    this.options = options;
  }

  /**
   * Start the webhook server
   */
  async start(): Promise<void> {
    return new Promise((resolve, reject) => {
      this.server = http.createServer((req, res) => {
        this.handleRequest(req, res).catch(err => {
          console.error('[TextMeOutbound] Request handler error:', err);
          this.sendError(res, 500, 'Internal server error');
        });
      });

      this.server.on('error', (err) => {
        console.error('[TextMeOutbound] Server error:', err);
        reject(err);
      });

      this.server.listen(this.options.port, () => {
        this.log(`Webhook server listening on port ${this.options.port}`);
        resolve();
      });
    });
  }

  /**
   * Handle incoming HTTP request
   */
  private async handleRequest(
    req: http.IncomingMessage,
    res: http.ServerResponse
  ): Promise<void> {
    const url = new URL(req.url || '/', `http://localhost:${this.options.port}`);
    const path = url.pathname;

    // CORS headers
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');

    // Handle preflight
    if (req.method === 'OPTIONS') {
      res.writeHead(204);
      res.end();
      return;
    }

    const prefix = this.options.pathPrefix;

    // Route handling
    if (path === `${prefix}/probe` && req.method === 'GET') {
      await this.handleProbe(res);
    } else if (path === `${prefix}/send` && req.method === 'POST') {
      await this.handleSend(req, res);
    } else if (path === `${prefix}/health` && req.method === 'GET') {
      await this.handleHealth(res);
    } else {
      this.sendError(res, 404, 'Not found');
    }
  }

  /**
   * Handle probe request (channel plugin interface)
   */
  private async handleProbe(res: http.ServerResponse): Promise<void> {
    const authenticatedAccounts = this.authManager.getAllAccounts();
    
    const response: ProbeResponse = {
      channel: 'textme',
      version: '1.0.0',
      capabilities: ['send', 'receive', 'attachments', 'multi-account'],
      accounts: this.accounts.map(acc => ({
        id: acc.id,
        name: acc.name,
        phoneNumbers: acc.phoneNumbers,
        enabled: acc.enabled,
      })),
      status: authenticatedAccounts.length > 0 ? 'ready' : 'offline',
    };

    this.sendJSON(res, 200, response);
  }

  /**
   * Handle health check
   */
  private async handleHealth(res: http.ServerResponse): Promise<void> {
    const accounts = this.authManager.getAllAccounts();
    const healthy = accounts.length > 0;

    this.sendJSON(res, healthy ? 200 : 503, {
      status: healthy ? 'healthy' : 'unhealthy',
      accounts: accounts.length,
      timestamp: new Date().toISOString(),
    });
  }

  /**
   * Handle send request
   */
  private async handleSend(
    req: http.IncomingMessage,
    res: http.ServerResponse
  ): Promise<void> {
    // Parse request body
    const body = await this.parseBody(req);
    
    if (!body) {
      this.sendError(res, 400, 'Invalid request body');
      return;
    }

    const request = body as OutboundRequest;

    // Validate required fields
    if (!request.target || !request.message) {
      this.sendError(res, 400, 'Missing required fields: target, message');
      return;
    }

    try {
      const result = await this.sendMessage(request);
      this.sendJSON(res, result.success ? 200 : 500, result);
    } catch (err) {
      console.error('[TextMeOutbound] Send error:', err);
      const errorMessage = err instanceof Error ? err.message : 'Unknown error';
      this.sendJSON(res, 500, {
        success: false,
        error: errorMessage,
      });
    }
  }

  /**
   * Send a message via TextMe
   */
  async sendMessage(request: OutboundRequest): Promise<OutboundResponse> {
    // Determine which account to use
    let account: AuthenticatedAccount | undefined;

    if (request.accountId) {
      account = this.authManager.getAccount(request.accountId);
    } else if (request.fromNumber) {
      account = this.authManager.getAccountByPhoneNumber(request.fromNumber);
    } else {
      account = this.authManager.getDefaultAccount();
    }

    if (!account) {
      return {
        success: false,
        error: 'No suitable account found for sending',
      };
    }

    this.log(`Sending message via account ${account.account.id} to ${request.target}`);

    try {
      // Parse conversation ID from target if it's a channel ID format
      let conversationId: number;
      
      if (request.target.startsWith('textme:')) {
        // Format: textme:accountId:conversationId
        const parts = request.target.split(':');
        conversationId = parseInt(parts[2], 10);
      } else if (/^\d+$/.test(request.target)) {
        // Direct conversation ID
        conversationId = parseInt(request.target, 10);
      } else {
        // Phone number - need to find or create conversation
        // For now, we'll assume it's a conversation ID
        // In a real implementation, you'd need to look up or create the conversation
        return {
          success: false,
          error: 'Phone number targets not yet supported. Use conversation ID.',
        };
      }

      // Send the message
      const result: SendMessageResponse = await account.api.sendMessage(
        conversationId,
        {
          body: request.message,
          type: 'text',
          attachment_ids: request.attachments?.map(a => parseInt(a, 10)).filter(n => !isNaN(n)),
          reply_to_id: request.replyTo ? parseInt(request.replyTo, 10) : undefined,
        }
      );

      return {
        success: true,
        messageId: result.message.id.toString(),
      };
    } catch (err) {
      console.error('[TextMeOutbound] API error:', err);
      const errorMessage = err instanceof Error ? err.message : 'Unknown error';
      return {
        success: false,
        error: errorMessage,
        details: err,
      };
    }
  }

  /**
   * Parse JSON request body
   */
  private async parseBody(req: http.IncomingMessage): Promise<unknown> {
    return new Promise((resolve) => {
      const chunks: Buffer[] = [];
      
      req.on('data', (chunk: Buffer) => {
        chunks.push(chunk);
      });

      req.on('end', () => {
        try {
          const body = Buffer.concat(chunks).toString('utf-8');
          resolve(JSON.parse(body));
        } catch {
          resolve(null);
        }
      });

      req.on('error', () => {
        resolve(null);
      });
    });
  }

  /**
   * Send JSON response
   */
  private sendJSON(
    res: http.ServerResponse,
    status: number,
    data: unknown
  ): void {
    res.writeHead(status, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify(data));
  }

  /**
   * Send error response
   */
  private sendError(
    res: http.ServerResponse,
    status: number,
    message: string
  ): void {
    this.sendJSON(res, status, { error: message });
  }

  /**
   * Stop the webhook server
   */
  async stop(): Promise<void> {
    return new Promise((resolve) => {
      if (this.server) {
        this.server.close(() => {
          this.log('Webhook server stopped');
          this.server = null;
          resolve();
        });
      } else {
        resolve();
      }
    });
  }

  /**
   * Get server address
   */
  getAddress(): string {
    return `http://localhost:${this.options.port}${this.options.pathPrefix}`;
  }

  /**
   * Debug logging
   */
  private log(message: string): void {
    if (this.options.debug) {
      console.log(`[TextMeOutbound] ${message}`);
    }
  }
}

// ============================================================================
// Factory
// ============================================================================

export function createOutboundHandler(
  authManager: AuthManager,
  config: PluginConfig
): OutboundHandler {
  return new OutboundHandler(authManager, config.accounts, {
    port: config.webhookPort,
    pathPrefix: config.webhookPath,
    debug: config.debug,
  });
}
