import type { FastifyBaseLogger } from 'fastify';
import {
  EventType,
  parseEventPayload,
  getEventTypeName,
  type UserEvent,
  type OrderEventPayload,
  type AlertEventPayload,
  type PositionEventPayload,
  type StrategyEventPayload,
} from './types.js';

/**
 * User channel configuration loaded from database.
 */
export interface UserChannelConfig {
  telegramChatId?: string;
  email?: string;
  pushToken?: string;
  discordWebhook?: string;
  slackWebhook?: string;
}

export interface DeliveryServiceConfig {
  telegramBotToken?: string;
  emailServiceKey?: string;
  emailFromAddress?: string;
  pushServiceKey?: string;
  logger: FastifyBaseLogger;
}

/**
 * DeliveryService handles actual delivery to external channels.
 *
 * Owns credentials for:
 * - Telegram bot
 * - Email service (SendGrid, SES, etc.)
 * - Push notifications (Firebase, APNs)
 * - Discord/Slack webhooks
 *
 * User-specific channel configs (chat IDs, emails, tokens) are loaded
 * from the database on demand.
 */
export class DeliveryService {
  private telegramBotToken?: string;
  private emailServiceKey?: string;
  private pushServiceKey?: string;
  private logger: FastifyBaseLogger;

  // Cache of user channel configs
  // In production, this should have TTL and be backed by Redis
  private userConfigs = new Map<string, UserChannelConfig>();

  constructor(config: DeliveryServiceConfig) {
    this.telegramBotToken = config.telegramBotToken;
    this.emailServiceKey = config.emailServiceKey;
    this.pushServiceKey = config.pushServiceKey;
    this.logger = config.logger.child({ component: 'DeliveryService' });
  }

  /**
   * Load user's channel configuration from database.
   * TODO: Implement actual database lookup.
   */
  async loadUserConfig(userId: string): Promise<UserChannelConfig> {
    // Check cache first
    const cached = this.userConfigs.get(userId);
    if (cached) return cached;

    // TODO: Load from database
    // For now, return empty config
    const config: UserChannelConfig = {};
    this.userConfigs.set(userId, config);
    return config;
  }

  /**
   * Update cached user config (called when user updates their settings).
   */
  updateUserConfig(userId: string, config: Partial<UserChannelConfig>): void {
    const existing = this.userConfigs.get(userId) || {};
    this.userConfigs.set(userId, { ...existing, ...config });
  }

  /**
   * Clear cached user config.
   */
  clearUserConfig(userId: string): void {
    this.userConfigs.delete(userId);
  }

  // ===========================================================================
  // Telegram
  // ===========================================================================

  /**
   * Send event notification via Telegram.
   */
  async sendTelegram(userId: string, event: UserEvent): Promise<void> {
    if (!this.telegramBotToken) {
      throw new Error('Telegram bot token not configured');
    }

    const config = await this.loadUserConfig(userId);
    if (!config.telegramChatId) {
      throw new Error('User has no Telegram chat ID configured');
    }

    const message = this.formatTelegramMessage(event);

    const response = await fetch(
      `https://api.telegram.org/bot${this.telegramBotToken}/sendMessage`,
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          chat_id: config.telegramChatId,
          text: message,
          parse_mode: 'Markdown',
        }),
      }
    );

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`Telegram API error: ${error}`);
    }

    this.logger.info(
      { userId, eventId: event.eventId, chatId: config.telegramChatId },
      'Sent Telegram notification'
    );
  }

  /**
   * Format event as Telegram message with Markdown.
   */
  private formatTelegramMessage(event: UserEvent): string {
    switch (event.eventType) {
      case EventType.ORDER_PLACED:
      case EventType.ORDER_FILLED:
      case EventType.ORDER_CANCELLED:
      case EventType.ORDER_REJECTED:
      case EventType.ORDER_EXPIRED: {
        const payload = parseEventPayload<OrderEventPayload>(event);
        if (!payload) return this.formatGenericMessage(event);

        const emoji = this.getOrderEmoji(event.eventType);
        const title = this.getOrderTitle(event.eventType);

        return (
          `${emoji} *${title}*\n\n` +
          `Symbol: \`${payload.symbol}\`\n` +
          `Side: ${payload.side.toUpperCase()}\n` +
          `Quantity: ${payload.quantity}\n` +
          (payload.fillPrice ? `Fill Price: ${payload.fillPrice}\n` : '') +
          (payload.price ? `Limit Price: ${payload.price}\n` : '') +
          `Exchange: ${payload.exchange}\n` +
          (payload.strategyId ? `Strategy: ${payload.strategyId}\n` : '') +
          (payload.errorMessage ? `Error: ${payload.errorMessage}\n` : '')
        );
      }

      case EventType.ALERT_TRIGGERED: {
        const payload = parseEventPayload<AlertEventPayload>(event);
        if (!payload) return this.formatGenericMessage(event);

        return (
          `🔔 *Alert Triggered*\n\n` +
          `Symbol: \`${payload.symbol}\`\n` +
          `Condition: ${payload.condition}\n` +
          `Price: ${payload.triggeredPrice}`
        );
      }

      case EventType.POSITION_OPENED:
      case EventType.POSITION_CLOSED:
      case EventType.POSITION_UPDATED:
      case EventType.POSITION_LIQUIDATED: {
        const payload = parseEventPayload<PositionEventPayload>(event);
        if (!payload) return this.formatGenericMessage(event);

        const emoji = this.getPositionEmoji(event.eventType);
        const title = this.getPositionTitle(event.eventType);

        return (
          `${emoji} *${title}*\n\n` +
          `Symbol: \`${payload.symbol}\`\n` +
          `Side: ${payload.side.toUpperCase()}\n` +
          `Size: ${payload.size}\n` +
          `Entry: ${payload.entryPrice}\n` +
          `Current: ${payload.currentPrice}\n` +
          `Unrealized PnL: ${payload.unrealizedPnl}\n` +
          (payload.realizedPnl ? `Realized PnL: ${payload.realizedPnl}\n` : '') +
          (payload.liquidationPrice ? `Liquidation: ${payload.liquidationPrice}\n` : '')
        );
      }

      case EventType.STRATEGY_ERROR: {
        const payload = parseEventPayload<StrategyEventPayload>(event);
        if (!payload) return this.formatGenericMessage(event);

        return (
          `🚨 *Strategy Error*\n\n` +
          `Strategy: ${payload.strategyName}\n` +
          `Message: ${payload.message}\n` +
          (payload.details ? `Details: ${payload.details}` : '')
        );
      }

      default:
        return this.formatGenericMessage(event);
    }
  }

  private formatGenericMessage(event: UserEvent): string {
    const payload = parseEventPayload(event);
    return (
      `📌 *${getEventTypeName(event.eventType)}*\n\n` +
      `\`\`\`\n${JSON.stringify(payload, null, 2)}\n\`\`\``
    );
  }

  private getOrderEmoji(eventType: EventType): string {
    switch (eventType) {
      case EventType.ORDER_FILLED:
        return '✅';
      case EventType.ORDER_PLACED:
        return '📝';
      case EventType.ORDER_CANCELLED:
        return '❌';
      case EventType.ORDER_REJECTED:
        return '🚫';
      case EventType.ORDER_EXPIRED:
        return '⏰';
      default:
        return '📋';
    }
  }

  private getOrderTitle(eventType: EventType): string {
    switch (eventType) {
      case EventType.ORDER_FILLED:
        return 'Order Filled';
      case EventType.ORDER_PLACED:
        return 'Order Placed';
      case EventType.ORDER_CANCELLED:
        return 'Order Cancelled';
      case EventType.ORDER_REJECTED:
        return 'Order Rejected';
      case EventType.ORDER_EXPIRED:
        return 'Order Expired';
      default:
        return 'Order Update';
    }
  }

  private getPositionEmoji(eventType: EventType): string {
    switch (eventType) {
      case EventType.POSITION_OPENED:
        return '📈';
      case EventType.POSITION_CLOSED:
        return '📉';
      case EventType.POSITION_UPDATED:
        return '📊';
      case EventType.POSITION_LIQUIDATED:
        return '💥';
      default:
        return '📊';
    }
  }

  private getPositionTitle(eventType: EventType): string {
    switch (eventType) {
      case EventType.POSITION_OPENED:
        return 'Position Opened';
      case EventType.POSITION_CLOSED:
        return 'Position Closed';
      case EventType.POSITION_UPDATED:
        return 'Position Updated';
      case EventType.POSITION_LIQUIDATED:
        return 'Position Liquidated';
      default:
        return 'Position Update';
    }
  }

  // ===========================================================================
  // Email
  // ===========================================================================

  /**
   * Send event notification via email.
   * TODO: Implement with SendGrid, SES, or similar.
   */
  async sendEmail(userId: string, event: UserEvent): Promise<void> {
    if (!this.emailServiceKey) {
      throw new Error('Email service not configured');
    }

    const config = await this.loadUserConfig(userId);
    if (!config.email) {
      throw new Error('User has no email configured');
    }

    const subject = this.formatEmailSubject(event);
    // const body = this.formatEmailBody(event);

    // TODO: Implement actual email sending
    // Example with SendGrid:
    // await sendgrid.send({
    //   to: config.email,
    //   from: this.emailFromAddress,
    //   subject,
    //   html: body,
    // });

    this.logger.info(
      { userId, eventId: event.eventId, email: config.email, subject },
      'Sent email notification (stub)'
    );
  }

  private formatEmailSubject(event: UserEvent): string {
    switch (event.eventType) {
      case EventType.ORDER_FILLED: {
        const payload = parseEventPayload<OrderEventPayload>(event);
        return `Order Filled: ${payload?.symbol || 'Unknown'}`;
      }
      case EventType.ALERT_TRIGGERED: {
        const payload = parseEventPayload<AlertEventPayload>(event);
        return `Alert: ${payload?.symbol || 'Unknown'} - ${payload?.condition || ''}`;
      }
      case EventType.POSITION_LIQUIDATED: {
        const payload = parseEventPayload<PositionEventPayload>(event);
        return `⚠️ Position Liquidated: ${payload?.symbol || 'Unknown'}`;
      }
      default:
        return `Dexorder: ${getEventTypeName(event.eventType)}`;
    }
  }



  // ===========================================================================
  // Push Notifications
  // ===========================================================================

  /**
   * Send push notification via Firebase/APNs.
   * TODO: Implement with Firebase Admin SDK.
   */
  async sendPush(userId: string, event: UserEvent): Promise<void> {
    if (!this.pushServiceKey) {
      throw new Error('Push service not configured');
    }

    const config = await this.loadUserConfig(userId);
    if (!config.pushToken) {
      throw new Error('User has no push token configured');
    }

    const title = this.formatPushTitle(event);
    // const body = this.formatPushBody(event);

    // TODO: Implement actual push notification
    // Example with Firebase:
    // await admin.messaging().send({
    //   token: config.pushToken,
    //   notification: { title, body },
    //   data: { eventId: event.eventId, eventType: String(event.eventType) },
    // });

    this.logger.info(
      { userId, eventId: event.eventId, title },
      'Sent push notification (stub)'
    );
  }

  private formatPushTitle(event: UserEvent): string {
    switch (event.eventType) {
      case EventType.ORDER_FILLED:
        return 'Order Filled';
      case EventType.ALERT_TRIGGERED:
        return 'Alert Triggered';
      case EventType.POSITION_LIQUIDATED:
        return '⚠️ Position Liquidated';
      default:
        return 'Dexorder';
    }
  }



  // ===========================================================================
  // Discord
  // ===========================================================================

  /**
   * Send event notification via Discord webhook.
   */
  async sendDiscord(userId: string, event: UserEvent): Promise<void> {
    const config = await this.loadUserConfig(userId);
    if (!config.discordWebhook) {
      throw new Error('User has no Discord webhook configured');
    }

    const embed = this.formatDiscordEmbed(event);

    const response = await fetch(config.discordWebhook, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ embeds: [embed] }),
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`Discord webhook error: ${error}`);
    }

    this.logger.info({ userId, eventId: event.eventId }, 'Sent Discord notification');
  }

  private formatDiscordEmbed(event: UserEvent): object {
    const payload = parseEventPayload(event);
    return {
      title: getEventTypeName(event.eventType),
      description: JSON.stringify(payload, null, 2).slice(0, 2000),
      color: this.getDiscordColor(event.eventType),
      timestamp: new Date(event.timestamp).toISOString(),
      footer: { text: `Event ID: ${event.eventId}` },
    };
  }

  private getDiscordColor(eventType: EventType): number {
    switch (eventType) {
      case EventType.ORDER_FILLED:
        return 0x00ff00; // Green
      case EventType.ORDER_REJECTED:
      case EventType.POSITION_LIQUIDATED:
      case EventType.STRATEGY_ERROR:
        return 0xff0000; // Red
      case EventType.ALERT_TRIGGERED:
        return 0xffff00; // Yellow
      default:
        return 0x0099ff; // Blue
    }
  }

  // ===========================================================================
  // Slack
  // ===========================================================================

  /**
   * Send event notification via Slack webhook.
   */
  async sendSlack(userId: string, event: UserEvent): Promise<void> {
    const config = await this.loadUserConfig(userId);
    if (!config.slackWebhook) {
      throw new Error('User has no Slack webhook configured');
    }

    const blocks = this.formatSlackBlocks(event);

    const response = await fetch(config.slackWebhook, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ blocks }),
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`Slack webhook error: ${error}`);
    }

    this.logger.info({ userId, eventId: event.eventId }, 'Sent Slack notification');
  }

  private formatSlackBlocks(event: UserEvent): object[] {
    const payload = parseEventPayload(event);
    return [
      {
        type: 'header',
        text: {
          type: 'plain_text',
          text: getEventTypeName(event.eventType),
        },
      },
      {
        type: 'section',
        text: {
          type: 'mrkdwn',
          text: '```' + JSON.stringify(payload, null, 2).slice(0, 2000) + '```',
        },
      },
      {
        type: 'context',
        elements: [
          {
            type: 'mrkdwn',
            text: `Event ID: ${event.eventId}`,
          },
        ],
      },
    ];
  }
}
