import { z } from 'zod';
import { BasePack } from '../base-pack.js';
import * as renderer from '../../apps/renderer.js';
import { 
  toE164, 
  isValidPhone, 
  messageStatusInfo, 
  timeAgo, 
  truncate 
} from '../../utils/validators.js';
import { estimateBatchCost, formatCost } from '../../utils/cost-calculator.js';

export class MessagingPack extends BasePack {
  readonly name = 'messaging';
  readonly description = 'SMS, MMS, WhatsApp messaging tools with campaigns and templates';
  readonly keywords = ['sms', 'text', 'message', 'campaign', 'template', 'conversation', 'thread'];

  setup(): void {
    // ─── twilio_campaign_launch ───
    this.registerTool({
      name: 'twilio_campaign_launch',
      description: 'Launch a bulk messaging campaign with template variables. Send personalized messages to multiple recipients.',
      category: 'messaging',
      safety: 'red',
      tier: 2,
      isApp: true,
      annotations: { 
        costHint: true, 
        estimatedCost: '~$0.0079/SMS (US)',
        destructiveHint: false,
      },
      inputSchema: z.object({
        recipients: z.array(z.string()).describe('Array of recipient phone numbers'),
        template: z.string().describe('Message template with {{variable}} placeholders'),
        variables: z.record(z.array(z.string())).optional().describe('Object mapping variable names to arrays of values (one per recipient)'),
        from: z.string().optional().describe('Sender phone number (auto-selected if omitted)'),
        messaging_service_sid: z.string().optional().describe('Messaging Service SID to use instead of from number'),
      }),
      _meta: {
        labels: {
          category: "messaging",
          access: "delete",
          complexity: "simple"
        }
      },
      handler: async (params) => {
        try {
          // Validate all phone numbers
          const validatedRecipients = params.recipients.map((r: string) => {
            const e164 = toE164(r);
            if (!isValidPhone(e164)) {
              throw new Error(`Invalid phone number: ${r}`);
            }
            return e164;
          });

          // Get sender
          let from = params.from;
          if (!from && !params.messaging_service_sid) {
            const numbers = await this.client.execute(c =>
              c.incomingPhoneNumbers.list({ limit: 1 })
            );
            if (numbers.length === 0) {
              return this.errorResult('No phone numbers available. Specify from or messaging_service_sid.');
            }
            from = numbers[0].phoneNumber;
          }

          // Extract template variables
          const templateVars: string[] = (params.template.match(/\{\{(\w+)\}\}/g) || [])
            .map((v: string) => v.replace(/[{}]/g, ''));

          // Prepare messages
          const messagesToSend: Array<{ to: string; body: string }> = [];
          for (let i = 0; i < validatedRecipients.length; i++) {
            let body = params.template;
            
            // Replace variables
            if (params.variables) {
              for (const varName of templateVars) {
                const values = params.variables[varName];
                if (values && values[i]) {
                  body = body.replace(new RegExp(`\\{\\{${varName}\\}\\}`, 'g'), values[i]);
                }
              }
            }
            
            messagesToSend.push({ to: validatedRecipients[i], body });
          }

          // Send messages in batch
          const results: Array<{ to: string; sid: string; status: string; error?: string }> = [];
          
          for (const msg of messagesToSend) {
            try {
              const msgParams: any = {
                to: msg.to,
                body: msg.body,
              };
              
              if (params.messaging_service_sid) {
                msgParams.messagingServiceSid = params.messaging_service_sid;
              } else {
                msgParams.from = toE164(from!);
              }

              const sent = await this.client.execute(c => c.messages.create(msgParams));
              results.push({
                to: msg.to,
                sid: sent.sid,
                status: sent.status,
              });
            } catch (err: any) {
              results.push({
                to: msg.to,
                sid: '',
                status: 'failed',
                error: err.message,
              });
            }
          }

          // Calculate stats
          const total = results.length;
          const sent = results.filter(r => r.status !== 'failed').length;
          const failed = results.filter(r => r.status === 'failed').length;
          const pending = results.filter(r => r.status === 'queued' || r.status === 'sending').length;
          const delivered = results.filter(r => r.status === 'delivered' || r.status === 'sent').length;

          // Estimate cost
          const cost = estimateBatchCost('sms_outbound_us', sent);

          // Build MCP App
          const html = renderer.appLayout(
            '📣 Campaign Results',
            `Sent ${sent} of ${total} messages · Est. cost: ${cost.formatted}`,
            `
            <div class="card-grid card-grid-4">
              ${renderer.metricCard(String(total), 'Total Recipients')}
              ${renderer.metricCard(String(delivered), 'Delivered/Sent')}
              ${renderer.metricCard(String(pending), 'Pending')}
              ${renderer.metricCard(String(failed), 'Failed')}
            </div>

            <div class="card">
              <div class="card-header">Campaign Details</div>
              ${renderer.table(
                ['Recipient', 'Status', 'Message SID'],
                results.map(r => {
                  const statusInfo = messageStatusInfo(r.status);
                  return [
                    renderer.formatPhone(r.to),
                    renderer.statusDot(statusInfo.color) + renderer.badge(statusInfo.label, statusInfo.color === 'green' ? 'success' : statusInfo.color === 'red' ? 'danger' : statusInfo.color === 'yellow' ? 'warning' : 'info'),
                    r.sid || (r.error ? `<span class="muted small">${truncate(r.error, 40)}</span>` : '—'),
                  ];
                })
              )}
            </div>
            `
          );

          return this.appResult(
            html,
            `Campaign sent: ${sent}/${total} messages delivered/sent, ${failed} failed. Est. cost: ${cost.formatted}`
          );
        } catch (err: any) {
          return this.errorResult(`Campaign failed: ${err.message}`);
        }
      },
    });

    // ─── twilio_conversation_thread ───
    this.registerTool({
      name: 'twilio_conversation_thread',
      description: 'View a conversation thread with a specific phone number — chat-style display of all messages to/from that number.',
      category: 'messaging',
      safety: 'green',
      tier: 2,
      isApp: true,
      annotations: { readOnlyHint: true },
      inputSchema: z.object({
        phone_number: z.string().describe('Phone number to view conversation with'),
        limit: z.number().optional().describe('Number of messages to retrieve (default: 20)'),
        since: z.string().optional().describe('Only show messages since this ISO date'),
      }),
      _meta: {
        labels: {
          category: "messaging",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => {
        const phone = toE164(params.phone_number);
        if (!isValidPhone(phone)) {
          return this.errorResult(`Invalid phone number: ${params.phone_number}`);
        }

        try {
          const limit = params.limit || 20;
          
          // Fetch messages to/from this number
          const listParams: any = { limit: limit * 2 }; // Fetch more since we filter both directions
          if (params.since) {
            listParams.dateSentAfter = new Date(params.since);
          }

          const allMessages = await this.client.execute(c => c.messages.list(listParams));
          
          // Filter to only messages involving this number
          const messages = allMessages
            .filter(m => m.to === phone || m.from === phone)
            .slice(0, limit)
            .sort((a, b) => (a.dateSent?.getTime() || 0) - (b.dateSent?.getTime() || 0));

          if (messages.length === 0) {
            return this.textResult(`No messages found with ${phone}`);
          }

          // Build chat bubble HTML
          const bubbles = messages.map(m => {
            const isOutbound = m.direction.startsWith('outbound');
            const statusInfo = messageStatusInfo(m.status);
            const bubbleClass = isOutbound ? 'chat-outbound' : 'chat-inbound';
            
            return `
              <div class="chat-bubble ${bubbleClass}">
                <div>${m.body || '<em>No text content</em>'}</div>
                <div class="chat-meta">
                  ${timeAgo(m.dateSent!)} · ${statusInfo.label}
                  ${m.numMedia && parseInt(m.numMedia) > 0 ? ' · 📎 ' + m.numMedia + ' media' : ''}
                </div>
              </div>
            `;
          }).join('');

          const html = renderer.appLayout(
            `💬 Conversation with ${renderer.formatPhone(phone)}`,
            `${messages.length} messages`,
            `
            <div class="card">
              ${bubbles}
            </div>
            `
          );

          return this.appResult(
            html,
            `Conversation with ${phone}: ${messages.length} messages`
          );
        } catch (err: any) {
          return this.errorResult(`Failed to load conversation: ${err.message}`);
        }
      },
    });

    // ─── twilio_template_create ───
    this.registerTool({
      name: 'twilio_template_create',
      description: 'Create a reusable message template with variables. Auto-extracts {{variables}} from template text.',
      category: 'messaging',
      safety: 'yellow',
      tier: 2,
      annotations: { idempotentHint: false },
      inputSchema: z.object({
        name: z.string().describe('Unique template name'),
        body: z.string().describe('Template text with {{variable}} placeholders'),
        channel: z.enum(['sms', 'whatsapp', 'both']).optional().describe('Channel(s) for this template (default: sms)'),
      }),
      _meta: {
        labels: {
          category: "messaging",
          access: "write",
          complexity: "simple"
        }
      },
      handler: async (params) => {
        try {
          // Extract variables from template
          const variables: string[] = (params.body.match(/\{\{(\w+)\}\}/g) || [])
            .map((v: string) => v.replace(/[{}]/g, ''));
          const uniqueVars: string[] = [...new Set(variables)];

          // Build Content API payload
          const channel = params.channel || 'sms';
          const types: any = {};
          
          if (channel === 'sms' || channel === 'both') {
            types['twilio/text'] = {
              body: params.body,
            };
          }
          
          if (channel === 'whatsapp' || channel === 'both') {
            types['twilio/text'] = {
              body: params.body,
            };
          }

          // Create content template
          const content: any = await this.client.execute(c =>
            (c as any).content.v1.contents.create({
              friendlyName: params.name,
              language: 'en',
              variables: uniqueVars.reduce((acc: Record<string, any>, v: string) => {
                acc[v] = { type: 'string' };
                return acc;
              }, {} as Record<string, any>),
              types,
            })
          );

          return this.textResult(
            `Template created!\n` +
            `  SID: ${content.sid}\n` +
            `  Name: ${params.name}\n` +
            `  Channel: ${channel}\n` +
            `  Variables: ${uniqueVars.length > 0 ? uniqueVars.join(', ') : 'none'}\n\n` +
            `Preview:\n${params.body}`
          );
        } catch (err: any) {
          return this.errorResult(`Failed to create template: ${err.message}`);
        }
      },
    });

    // ─── twilio_auto_responder ───
    this.registerTool({
      name: 'twilio_auto_responder',
      description: 'Set up an auto-responder for a phone number. Create a Studio flow that sends an automatic reply when someone texts a keyword (or any text).',
      category: 'messaging',
      safety: 'yellow',
      tier: 2,
      annotations: { idempotentHint: false },
      inputSchema: z.object({
        number: z.string().describe('Phone number to configure'),
        trigger_text: z.string().optional().describe('Keyword to trigger the response (e.g., "HOURS")'),
        response: z.string().describe('Auto-reply text to send'),
        match_mode: z.enum(['exact', 'contains', 'any']).optional().describe('How to match trigger_text: exact match, contains, or respond to any message (default: any)'),
      }),
      _meta: {
        labels: {
          category: "messaging",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => {
        const phone = toE164(params.number);
        if (!isValidPhone(phone)) {
          return this.errorResult(`Invalid phone number: ${params.number}`);
        }

        try {
          const matchMode = params.match_mode || 'any';

          // Create a Studio flow for the auto-responder
          const flowDefinition = {
            description: `Auto-responder for ${phone}`,
            states: [
              {
                name: 'Trigger',
                type: 'trigger',
                transitions: [
                  {
                    event: 'incomingMessage',
                    next: 'send_response',
                  },
                ],
                properties: {},
              },
              {
                name: 'send_response',
                type: 'send-message',
                transitions: [],
                properties: {
                  body: params.response,
                },
              },
            ],
            initial_state: 'Trigger',
            flags: {
              allow_concurrent_calls: true,
            },
          };

          const flow: any = await this.client.execute(c =>
            (c as any).studio.v2.flows.create({
              friendlyName: `Auto-responder: ${phone}`,
              status: 'published',
              definition: flowDefinition,
            })
          );

          // Update the phone number to use this flow
          const numbers = await this.client.execute(c =>
            c.incomingPhoneNumbers.list({ phoneNumber: phone })
          );

          if (numbers.length === 0) {
            return this.errorResult(`Phone number ${phone} not found in your account`);
          }

          await this.client.execute(c =>
            c.incomingPhoneNumbers(numbers[0].sid).update({
              smsUrl: `https://webhooks.twilio.com/v1/Accounts/${this.client.accountSid}/Flows/${flow.sid}`,
            })
          );

          return this.textResult(
            `Auto-responder configured!\n` +
            `  Number: ${phone}\n` +
            `  Flow SID: ${flow.sid}\n` +
            `  Trigger: ${params.trigger_text || 'any message'}\n` +
            `  Match Mode: ${matchMode}\n` +
            `  Response: "${params.response}"\n\n` +
            `The auto-responder is now active.`
          );
        } catch (err: any) {
          return this.errorResult(`Failed to create auto-responder: ${err.message}`);
        }
      },
    });

    // ─── twilio_schedule_message ───
    this.registerTool({
      name: 'twilio_schedule_message',
      description: 'Schedule a message to be sent at a specific date/time. Uses Messaging Services with scheduling enabled.',
      category: 'messaging',
      safety: 'yellow',
      tier: 2,
      annotations: { 
        costHint: true, 
        estimatedCost: '~$0.0079/SMS (US)',
      },
      inputSchema: z.object({
        to: z.string().describe('Recipient phone number'),
        body: z.string().describe('Message text'),
        send_at: z.string().describe('ISO 8601 datetime when to send (e.g., 2024-12-25T09:00:00-05:00)'),
        from: z.string().optional().describe('Sender phone number'),
        timezone: z.string().optional().describe('Timezone for send_at (default: America/New_York)'),
      }),
      _meta: {
        labels: {
          category: "messaging",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => {
        const to = toE164(params.to);
        if (!isValidPhone(to)) {
          return this.errorResult(`Invalid phone number: ${params.to}`);
        }

        try {
          const sendAt = new Date(params.send_at);
          if (isNaN(sendAt.getTime())) {
            return this.errorResult(`Invalid date format: ${params.send_at}`);
          }

          // Check if send_at is in the future
          if (sendAt <= new Date()) {
            return this.errorResult('send_at must be in the future');
          }

          // Get or create a messaging service with scheduling
          let messagingServiceSid: string | undefined;
          
          const services: any[] = await this.client.execute(c =>
            (c as any).messaging.v1.services.list({ limit: 20 })
          );
          
          const schedulingService = services.find((s: any) => 
            s.friendlyName?.includes('Scheduled') || s.usecase === 'marketing'
          );

          if (schedulingService) {
            messagingServiceSid = schedulingService.sid;
          } else {
            // Create a new messaging service
            const newService: any = await this.client.execute(c =>
              (c as any).messaging.v1.services.create({
                friendlyName: 'Scheduled Messages',
                usecase: 'marketing',
              })
            );
            messagingServiceSid = newService.sid;

            // Add a sender to the service
            if (params.from) {
              await this.client.execute(c =>
                (c as any).messaging.v1.services(messagingServiceSid!)
                  .phoneNumbers.create({
                    phoneNumberSid: params.from,
                  })
              );
            }
          }

          // Schedule the message
          const message = await this.client.execute(c =>
            c.messages.create({
              to,
              body: params.body,
              messagingServiceSid,
              scheduleType: 'fixed',
              sendAt: sendAt,
            })
          );

          return this.textResult(
            `Message scheduled!\n` +
            `  SID: ${message.sid}\n` +
            `  To: ${to}\n` +
            `  Send At: ${sendAt.toLocaleString('en-US', { timeZone: params.timezone || 'America/New_York' })}\n` +
            `  Status: ${message.status}\n\n` +
            `The message will be sent at the scheduled time.`
          );
        } catch (err: any) {
          return this.errorResult(`Failed to schedule message: ${err.message}`);
        }
      },
    });

    // ─── twilio_message_log ───
    this.registerTool({
      name: 'twilio_message_log',
      description: 'Search and filter message logs with detailed table view. Filter by sender, recipient, date range, status, direction.',
      category: 'messaging',
      safety: 'green',
      tier: 2,
      isApp: true,
      annotations: { readOnlyHint: true },
      inputSchema: z.object({
        from: z.string().optional().describe('Filter by sender phone number'),
        to: z.string().optional().describe('Filter by recipient phone number'),
        date_start: z.string().optional().describe('Start date (ISO format)'),
        date_end: z.string().optional().describe('End date (ISO format)'),
        status: z.string().optional().describe('Filter by status (e.g., delivered, failed, sent)'),
        direction: z.enum(['inbound', 'outbound']).optional().describe('Filter by direction'),
        limit: z.number().optional().describe('Maximum results to return (default: 50)'),
      }),
      _meta: {
        labels: {
          category: "messaging",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => {
        try {
          const listParams: any = { limit: params.limit || 50 };
          
          if (params.from) {
            listParams.from = toE164(params.from);
          }
          if (params.to) {
            listParams.to = toE164(params.to);
          }
          if (params.date_start) {
            listParams.dateSentAfter = new Date(params.date_start);
          }
          if (params.date_end) {
            listParams.dateSentBefore = new Date(params.date_end);
          }

          let messages = await this.client.execute(c => c.messages.list(listParams));

          // Apply additional filters
          if (params.status) {
            messages = messages.filter(m => m.status === params.status);
          }
          if (params.direction) {
            messages = messages.filter(m => 
              params.direction === 'outbound' 
                ? m.direction.startsWith('outbound')
                : m.direction === 'inbound'
            );
          }

          if (messages.length === 0) {
            return this.textResult('No messages found matching the filters');
          }

          // Calculate total cost
          const totalCost = messages.reduce((sum, m) => {
            const price = parseFloat(m.price || '0');
            return sum + Math.abs(price);
          }, 0);

          // Build table
          const html = renderer.appLayout(
            '📋 Message Log',
            `${messages.length} messages · Total cost: ${formatCost(totalCost)}`,
            `
            <div class="card">
              ${renderer.table(
                ['Direction', 'From', 'To', 'Body', 'Status', 'Date', 'Cost'],
                messages.map(m => {
                  const statusInfo = messageStatusInfo(m.status);
                  const isOutbound = m.direction.startsWith('outbound');
                  const directionIcon = isOutbound ? '→' : '←';
                  const price = m.price ? formatCost(Math.abs(parseFloat(m.price))) : '—';
                  
                  return [
                    `<span style="font-size: 16px;">${directionIcon}</span>`,
                    `<span class="small">${renderer.formatPhone(m.from || '')}</span>`,
                    `<span class="small">${renderer.formatPhone(m.to || '')}</span>`,
                    `<span class="small">${truncate(m.body || '', 40)}</span>`,
                    renderer.statusDot(statusInfo.color) + renderer.badge(statusInfo.label, statusInfo.color === 'green' ? 'success' : statusInfo.color === 'red' ? 'danger' : statusInfo.color === 'yellow' ? 'warning' : 'info'),
                    `<span class="small muted">${timeAgo(m.dateSent!)}</span>`,
                    `<span class="small">${price}</span>`,
                  ];
                })
              )}
            </div>
            `
          );

          return this.appResult(
            html,
            `Found ${messages.length} messages. Total cost: ${formatCost(totalCost)}`
          );
        } catch (err: any) {
          return this.errorResult(`Failed to retrieve messages: ${err.message}`);
        }
      },
    });
  }
}
