import { z } from 'zod';
import { BasePack } from '../base-pack.js';
import * as renderer from '../../apps/renderer.js';

export class AnalyticsPack extends BasePack {
  readonly name = 'analytics';
  readonly description = 'Cost reporting, usage alerts, health monitoring, and error tracking';
  readonly keywords = ['cost', 'usage', 'spending', 'alert', 'health', 'error', 'monitor', 'budget'];

  setup(): void {
    // ─── twilio_cost_report ───
    this.registerTool({
      name: 'twilio_cost_report',
      description: 'Generate a detailed cost report with metrics, daily spend sparkline, and category breakdown. Supports various time periods and category filters.',
      category: 'analytics',
      safety: 'green',
      tier: 2,
      isApp: true,
      annotations: { readOnlyHint: true },
      inputSchema: z.object({
        period: z.enum(['today', 'this_week', 'this_month', 'last_month', 'custom']).optional()
          .describe('Time period for the report (default: this_month)'),
        start_date: z.string().optional().describe('Start date for custom period (YYYY-MM-DD)'),
        end_date: z.string().optional().describe('End date for custom period (YYYY-MM-DD)'),
        category: z.enum(['sms', 'calls', 'phone-numbers', 'recordings', 'all']).optional()
          .describe('Filter by category (default: all)'),
      }),
      _meta: {
        labels: {
          category: "analytics",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => {
        try {
          const period = params.period || 'this_month';
          let startDate: Date = new Date();
          let endDate: Date = new Date();

          // Calculate date range
          switch (period) {
            case 'today':
              startDate = new Date();
              startDate.setHours(0, 0, 0, 0);
              break;
            case 'this_week':
              startDate = new Date();
              startDate.setDate(startDate.getDate() - startDate.getDay());
              startDate.setHours(0, 0, 0, 0);
              break;
            case 'this_month':
              startDate = new Date();
              startDate.setDate(1);
              startDate.setHours(0, 0, 0, 0);
              break;
            case 'last_month':
              endDate = new Date();
              endDate.setDate(0); // Last day of previous month
              endDate.setHours(23, 59, 59, 999);
              startDate = new Date(endDate);
              startDate.setDate(1);
              startDate.setHours(0, 0, 0, 0);
              break;
            case 'custom':
              if (!params.start_date || !params.end_date) {
                return this.errorResult('start_date and end_date are required for custom period');
              }
              startDate = new Date(params.start_date);
              endDate = new Date(params.end_date);
              break;
          }

          // Fetch usage records
          const usageParams: any = {
            startDate: startDate,
            endDate: endDate,
          };

          if (params.category && params.category !== 'all') {
            usageParams.category = params.category;
          }

          const records: any[] = await this.client.execute(c =>
            c.usage.records.list(usageParams)
          );

          // Group by category and calculate totals
          const categoryTotals = new Map<string, { usage: number; price: number; unit: string; countUnit: string }>();
          const dailySpend = new Map<string, number>();
          let totalSpend = 0;

          for (const record of records) {
            const price = parseFloat(record.price || '0');
            totalSpend += Math.abs(price);

            // Category totals
            const category = record.category || 'other';
            const existing = categoryTotals.get(category) || { usage: 0, price: 0, unit: record.priceUnit || 'USD', countUnit: record.countUnit || 'units' };
            existing.usage += parseInt(String(record.usage || '0'));
            existing.price += Math.abs(price);
            categoryTotals.set(category, existing);

            // Daily spend
            const date = record.startDate ? new Date(record.startDate).toISOString().split('T')[0] : 'unknown';
            dailySpend.set(date, (dailySpend.get(date) || 0) + Math.abs(price));
          }

          // Calculate category-specific totals
          let smsTotal = 0;
          let voiceTotal = 0;
          let numberTotal = 0;
          
          for (const [cat, data] of categoryTotals) {
            if (cat.includes('sms') || cat.includes('mms')) {
              smsTotal += data.price;
            } else if (cat.includes('calls') || cat.includes('voice')) {
              voiceTotal += data.price;
            } else if (cat.includes('phonenumber') || cat.includes('phone-number')) {
              numberTotal += data.price;
            }
          }

          // Build sparkline data
          const sparklineData: number[] = [];
          const sortedDates = Array.from(dailySpend.keys()).sort();
          for (const date of sortedDates.slice(-14)) { // Last 14 days
            sparklineData.push(dailySpend.get(date) || 0);
          }

          // Build table rows
          const tableRows: string[][] = [];
          for (const [category, data] of Array.from(categoryTotals.entries()).sort((a, b) => b[1].price - a[1].price)) {
            tableRows.push([
              category,
              data.usage.toLocaleString(),
              data.countUnit,
              `$${data.price.toFixed(2)}`,
            ]);
          }

          // Build HTML
          const html = renderer.appLayout(
            '💰 Cost Report',
            `${period === 'custom' ? `${params.start_date} to ${params.end_date}` : period.replace('_', ' ')} • ${params.category || 'all categories'}`,
            `
            <div class="card-grid card-grid-4">
              ${renderer.metricCard(`$${totalSpend.toFixed(2)}`, 'Total Spend')}
              ${renderer.metricCard(`$${smsTotal.toFixed(2)}`, 'SMS/MMS')}
              ${renderer.metricCard(`$${voiceTotal.toFixed(2)}`, 'Voice')}
              ${renderer.metricCard(`$${numberTotal.toFixed(2)}`, 'Phone Numbers')}
            </div>

            <div class="card">
              <div class="card-header">📈 Daily Spend (Last 14 Days)</div>
              ${renderer.sparkline(sparklineData)}
            </div>

            <div class="card">
              <div class="card-header">📊 Breakdown by Category</div>
              ${tableRows.length > 0 
                ? renderer.table(['Category', 'Usage', 'Unit', 'Cost'], tableRows)
                : '<p class="muted">No usage records found for this period.</p>'}
            </div>
            `
          );

          return this.appResult(html,
            `Cost Report (${period}): Total: $${totalSpend.toFixed(2)}, SMS: $${smsTotal.toFixed(2)}, Voice: $${voiceTotal.toFixed(2)}, Numbers: $${numberTotal.toFixed(2)}`
          );
        } catch (err: any) {
          return this.errorResult(`Failed to generate cost report: ${err.message}`);
        }
      },
    });

    // ─── twilio_usage_alert ───
    this.registerTool({
      name: 'twilio_usage_alert',
      description: 'Create a usage trigger that alerts you when spending exceeds a threshold. Supports daily and monthly triggers.',
      category: 'analytics',
      safety: 'yellow',
      tier: 2,
      annotations: { idempotentHint: false },
      inputSchema: z.object({
        friendly_name: z.string().describe('Name for this alert'),
        trigger_type: z.enum(['daily', 'monthly']).describe('Alert frequency'),
        category: z.string().describe('Usage category (e.g., sms, calls, totalprice)'),
        threshold: z.string().describe('Dollar amount threshold (e.g., "50.00")'),
        callback_url: z.string().optional().describe('URL to POST to when triggered'),
      }),
      _meta: {
        labels: {
          category: "analytics",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => {
        try {
          const recurringMap: Record<string, string> = {
            daily: 'daily',
            monthly: 'monthly',
          };

          const trigger = await this.client.execute(c =>
            c.usage.triggers.create({
              friendlyName: params.friendly_name,
              usageCategory: params.category,
              triggerValue: params.threshold,
              recurring: recurringMap[params.trigger_type] as any,
              callbackUrl: params.callback_url,
            })
          );

          return this.textResult(
            `Usage alert created!\n` +
            `  SID: ${trigger.sid}\n` +
            `  Name: ${trigger.friendlyName}\n` +
            `  Type: ${params.trigger_type}\n` +
            `  Category: ${params.category}\n` +
            `  Threshold: $${params.threshold}\n` +
            `  Callback: ${params.callback_url || 'none'}\n` +
            `  Status: ${trigger.currentValue ? `Current value: $${trigger.currentValue}` : 'Active'}`
          );
        } catch (err: any) {
          return this.errorResult(`Failed to create usage alert: ${err.message}`);
        }
      },
    });

    // ─── twilio_health_check ───
    this.registerTool({
      name: 'twilio_health_check',
      description: 'Run a comprehensive health check on your Twilio account: account status, message delivery rate, call answer rate, phone number count, and balance.',
      category: 'analytics',
      safety: 'green',
      tier: 2,
      isApp: true,
      annotations: { readOnlyHint: true },
      inputSchema: z.object({}),
      _meta: {
        labels: {
          category: "analytics",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async () => {
        try {
          // Account status
          const account = await this.client.execute(c =>
            c.api.accounts(this.client.accountSid).fetch()
          );
          const accountStatus = account.status;
          const accountColor = accountStatus === 'active' ? 'green' : 'red';

          // Balance
          let balance = 'N/A';
          let balanceColor: 'green' | 'yellow' | 'red' = 'green';
          try {
            const bal: any = await this.client.execute(c =>
              (c as any).api.accounts(this.client.accountSid).balance.fetch()
            );
            const balanceNum = parseFloat(bal.balance);
            balance = `$${balanceNum.toFixed(2)}`;
            if (balanceNum < 5) balanceColor = 'red';
            else if (balanceNum < 20) balanceColor = 'yellow';
          } catch { /* balance API may not be available */ }

          // Message delivery rate (last 100)
          const messages = await this.client.execute(c =>
            c.messages.list({ limit: 100 })
          );
          const outboundMessages = messages.filter(m => m.direction.startsWith('outbound'));
          const deliveredMessages = outboundMessages.filter(m => m.status === 'delivered' || m.status === 'sent');
          const failedMessages = outboundMessages.filter(m => m.status === 'failed' || m.status === 'undelivered');
          const messageHealthPercent = outboundMessages.length > 0 
            ? (deliveredMessages.length / outboundMessages.length) * 100 
            : 100;
          const messageHealthColor = messageHealthPercent >= 95 ? 'green' : messageHealthPercent >= 80 ? 'yellow' : 'red';

          // Call answer rate (last 100)
          const calls = await this.client.execute(c =>
            c.calls.list({ limit: 100 })
          );
          const outboundCalls = calls.filter(c => c.direction.startsWith('outbound'));
          const completedCalls = outboundCalls.filter(c => c.status === 'completed');
          const callHealthPercent = outboundCalls.length > 0 
            ? (completedCalls.length / outboundCalls.length) * 100 
            : 100;
          const callHealthColor = callHealthPercent >= 70 ? 'green' : callHealthPercent >= 50 ? 'yellow' : 'red';

          // Phone numbers
          const numbers = await this.client.execute(c =>
            c.incomingPhoneNumbers.list({ limit: 100 })
          );

          // Build HTML
          const html = renderer.appLayout(
            '🏥 Health Check',
            `Account: ${account.friendlyName}`,
            `
            <div class="card">
              <div class="card-header">System Health</div>
              <div style="padding: 8px 0;">
                <div class="list-item">
                  ${renderer.statusDot(accountColor)}
                  <div style="flex: 1;">
                    <strong>Account Status</strong>
                    <div class="small muted">${accountStatus}</div>
                  </div>
                  ${renderer.badge(accountStatus === 'active' ? 'Active' : 'Issue', accountStatus === 'active' ? 'success' : 'danger')}
                </div>
                <div class="list-item">
                  ${renderer.statusDot(messageHealthColor)}
                  <div style="flex: 1;">
                    <strong>Messaging Health</strong>
                    <div class="small muted">${deliveredMessages.length} delivered / ${failedMessages.length} failed (last 100)</div>
                    ${renderer.progressBar(messageHealthPercent, messageHealthColor)}
                  </div>
                  ${renderer.badge(`${messageHealthPercent.toFixed(0)}%`, messageHealthColor === 'green' ? 'success' : messageHealthColor === 'yellow' ? 'warning' : 'danger')}
                </div>
                <div class="list-item">
                  ${renderer.statusDot(callHealthColor)}
                  <div style="flex: 1;">
                    <strong>Voice Health</strong>
                    <div class="small muted">${completedCalls.length} completed / ${outboundCalls.length} total (last 100)</div>
                    ${renderer.progressBar(callHealthPercent, callHealthColor)}
                  </div>
                  ${renderer.badge(`${callHealthPercent.toFixed(0)}%`, callHealthColor === 'green' ? 'success' : callHealthColor === 'yellow' ? 'warning' : 'danger')}
                </div>
                <div class="list-item">
                  ${renderer.statusDot('blue')}
                  <div style="flex: 1;">
                    <strong>Phone Numbers</strong>
                    <div class="small muted">Active numbers in account</div>
                  </div>
                  ${renderer.badge(`${numbers.length}`, 'info')}
                </div>
                <div class="list-item">
                  ${renderer.statusDot(balanceColor)}
                  <div style="flex: 1;">
                    <strong>Balance</strong>
                    <div class="small muted">${balanceColor === 'red' ? 'Low balance - recharge soon' : balanceColor === 'yellow' ? 'Balance getting low' : 'Healthy balance'}</div>
                  </div>
                  ${renderer.badge(balance, balanceColor === 'green' ? 'success' : balanceColor === 'yellow' ? 'warning' : 'danger')}
                </div>
              </div>
            </div>
            `
          );

          return this.appResult(html,
            `Health Check: Account ${accountStatus}, Messaging ${messageHealthPercent.toFixed(0)}%, Voice ${callHealthPercent.toFixed(0)}%, Numbers ${numbers.length}, Balance ${balance}`
          );
        } catch (err: any) {
          return this.errorResult(`Health check failed: ${err.message}`);
        }
      },
    });

    // ─── twilio_error_log ───
    this.registerTool({
      name: 'twilio_error_log',
      description: 'View recent errors and alerts from Twilio Monitor. Shows error codes, messages, and affected resources.',
      category: 'analytics',
      safety: 'green',
      tier: 2,
      isApp: true,
      annotations: { readOnlyHint: true },
      inputSchema: z.object({
        date_start: z.string().optional().describe('Start date (YYYY-MM-DD)'),
        date_end: z.string().optional().describe('End date (YYYY-MM-DD)'),
        limit: z.number().optional().describe('Max number of errors to return (default: 50)'),
      }),
      _meta: {
        labels: {
          category: "analytics",
          access: "read",
          complexity: "simple"
        }
      },
      handler: async (params) => {
        try {
          const limit = params.limit || 50;
          const errors: Array<{ date: string; code: string; message: string; sid: string; severity: string }> = [];

          try {
            // Try to fetch from Monitor API
            const alertParams: any = { limit };
            if (params.date_start) {
              alertParams.startDate = new Date(params.date_start);
            }
            if (params.date_end) {
              alertParams.endDate = new Date(params.date_end);
            }

            const alerts: any[] = await this.client.execute(c =>
              (c as any).monitor.v1.alerts.list(alertParams)
            );

            for (const alert of alerts) {
              errors.push({
                date: alert.dateCreated ? new Date(alert.dateCreated).toLocaleString() : 'Unknown',
                code: alert.errorCode || 'N/A',
                message: alert.alertText || alert.logLevel || 'Unknown error',
                sid: alert.sid,
                severity: alert.logLevel || 'warning',
              });
            }
          } catch {
            // Fallback: check failed messages and calls
            const failedMessages = await this.client.execute(c =>
              c.messages.list({ limit: limit })
            );
            
            for (const msg of failedMessages.filter(m => m.status === 'failed' || m.status === 'undelivered')) {
              errors.push({
                date: msg.dateCreated ? new Date(msg.dateCreated).toLocaleString() : 'Unknown',
                code: msg.errorCode?.toString() || 'N/A',
                message: msg.errorMessage || `Message ${msg.status}`,
                sid: msg.sid,
                severity: 'error',
              });
            }

            const failedCalls = await this.client.execute(c =>
              c.calls.list({ limit: Math.max(0, limit - errors.length) })
            );

            for (const call of failedCalls.filter(c => c.status === 'failed')) {
              errors.push({
                date: call.dateCreated ? new Date(call.dateCreated).toLocaleString() : 'Unknown',
                code: 'N/A',
                message: 'Call failed',
                sid: call.sid,
                severity: 'error',
              });
            }
          }

          // Sort by date (most recent first)
          errors.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());

          // Build table rows
          const tableRows: string[][] = errors.map(err => {
            const severityBadge = err.severity === 'error' 
              ? renderer.badge('Error', 'danger')
              : err.severity === 'warning'
              ? renderer.badge('Warning', 'warning')
              : renderer.badge('Info', 'info');

            return [
              err.date,
              err.code,
              err.message,
              `<code style="font-size: 10px;">${err.sid}</code>`,
              severityBadge,
            ];
          });

          // Build HTML
          const html = renderer.appLayout(
            '🚨 Error Log',
            `Last ${limit} errors and alerts`,
            `
            <div class="card">
              ${tableRows.length > 0 
                ? renderer.table(['Date', 'Error Code', 'Message', 'Resource SID', 'Severity'], tableRows)
                : '<p class="muted">No errors found in the specified time period. 🎉</p>'}
            </div>
            `
          );

          return this.appResult(html,
            errors.length > 0
              ? `Found ${errors.length} errors/alerts. Most recent: ${errors[0].message} (${errors[0].code})`
              : 'No errors found.'
          );
        } catch (err: any) {
          return this.errorResult(`Failed to fetch error log: ${err.message}`);
        }
      },
    });
  }
}
