import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { SignalService } from './signal.service';
import { MessageDirection, EventStatus } from '@prisma/client';

export interface SendMessageDto {
  driverId: string;
  content: string;
}

export interface MessageWithDriver {
  id: string;
  driverId: string;
  direction: MessageDirection;
  content: string;
  timestamp: Date;
  isRead: boolean;
  driver: {
    id: string;
    name: string;
    phone: string;
  };
}

@Injectable()
export class MessagesService {
  private readonly logger = new Logger(MessagesService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly signalService: SignalService,
  ) {}

  /**
   * Get all messages for a driver
   */
  async getMessagesForDriver(driverId: string, limit: number = 50) {
    const driver = await this.prisma.driver.findFirst({
      where: { id: driverId, deletedAt: null },
    });

    if (!driver) {
      throw new NotFoundException(`Driver with ID ${driverId} not found`);
    }

    return this.prisma.signalMessage.findMany({
      where: { driverId },
      orderBy: { timestamp: 'desc' },
      take: limit,
    });
  }

  /**
   * Send a message to a driver
   */
  async sendMessage(dto: SendMessageDto) {
    const driver = await this.prisma.driver.findFirst({
      where: { id: dto.driverId, deletedAt: null },
    });

    if (!driver) {
      throw new NotFoundException(`Driver with ID ${dto.driverId} not found`);
    }

    // Get the linked Signal number
    const fromNumber = await this.signalService.getLinkedNumber();
    if (!fromNumber) {
      throw new Error('No Signal account linked. Please link an account in Admin Tools.');
    }

    // Check driver has a phone number
    if (!driver.phone) {
      throw new Error('Driver does not have a phone number configured.');
    }

    // Format the driver's phone number
    const toNumber = this.signalService.formatPhoneNumber(driver.phone);

    // Send via Signal
    const result = await this.signalService.sendMessage(fromNumber, toNumber, dto.content);

    if (!result.success) {
      throw new Error(result.error || 'Failed to send message via Signal');
    }

    // Store the message in database
    const message = await this.prisma.signalMessage.create({
      data: {
        driverId: dto.driverId,
        direction: MessageDirection.OUTBOUND,
        content: dto.content,
        isRead: true, // Outbound messages are always "read"
        signalTimestamp: result.timestamp?.toString(),
      },
    });

    this.logger.log(`Message sent to driver ${driver.name} (${toNumber})`);

    return message;
  }

  /**
   * Process incoming message from Signal webhook
   */
  async processIncomingMessage(
    fromNumber: string,
    content: string,
    signalTimestamp?: string,
  ) {
    // Normalize phone number for matching
    const normalizedPhone = this.normalizePhoneForSearch(fromNumber);

    // Find driver by phone number
    const driver = await this.prisma.driver.findFirst({
      where: {
        deletedAt: null,
        OR: [
          { phone: fromNumber },
          { phone: normalizedPhone },
          { phone: { contains: normalizedPhone.slice(-10) } }, // Last 10 digits
        ],
      },
    });

    if (!driver) {
      this.logger.warn(`Received message from unknown number: ${fromNumber}`);
      return null;
    }

    // Check for duplicate message
    if (signalTimestamp) {
      const existing = await this.prisma.signalMessage.findFirst({
        where: {
          driverId: driver.id,
          signalTimestamp,
        },
      });
      if (existing) {
        this.logger.debug(`Duplicate message ignored: ${signalTimestamp}`);
        return existing;
      }
    }

    // Store the message
    const message = await this.prisma.signalMessage.create({
      data: {
        driverId: driver.id,
        direction: MessageDirection.INBOUND,
        content,
        isRead: false,
        signalTimestamp,
      },
    });

    this.logger.log(`Incoming message from driver ${driver.name}: ${content.substring(0, 50)}...`);

    // Check if this is a status response (1, 2, or 3)
    const trimmedContent = content.trim();
    if (['1', '2', '3'].includes(trimmedContent)) {
      await this.processDriverStatusResponse(driver, parseInt(trimmedContent, 10));
    }

    return message;
  }

  /**
   * Process a driver's status response (1=Confirmed, 2=Delayed, 3=Issue)
   */
  private async processDriverStatusResponse(driver: any, response: number) {
    // Find the driver's current IN_PROGRESS event
    const activeEvent = await this.prisma.scheduleEvent.findFirst({
      where: {
        driverId: driver.id,
        status: EventStatus.IN_PROGRESS,
        deletedAt: null,
      },
      include: { vehicle: true },
    });

    if (!activeEvent) {
      // No active event, send a clarification
      await this.sendAutoReply(driver, 'No active trip found for your response. If you need assistance, please send a message to the coordinator.');
      return;
    }

    const now = new Date();
    let replyMessage: string;
    let noteText: string;

    switch (response) {
      case 1: // Confirmed
        noteText = `[${now.toLocaleTimeString()}] ✅ Driver confirmed en route`;
        replyMessage = `✅ Confirmed! Safe travels with your VIP. Reply when completed or if you need assistance.`;
        break;

      case 2: // Delayed
        noteText = `[${now.toLocaleTimeString()}] ⏰ Driver reported DELAY - awaiting details`;
        replyMessage = `⏰ Delay noted. Please reply with the reason for the delay. The coordinator has been alerted.`;
        break;

      case 3: // Issue
        noteText = `[${now.toLocaleTimeString()}] 🚨 Driver reported ISSUE - needs help`;
        replyMessage = `🚨 Issue reported! A coordinator will contact you shortly. Please describe the problem in your next message.`;
        break;

      default:
        return;
    }

    // Update the event with the driver's response
    await this.prisma.scheduleEvent.update({
      where: { id: activeEvent.id },
      data: {
        notes: activeEvent.notes
          ? `${activeEvent.notes}\n${noteText}`
          : noteText,
      },
    });

    this.logger.log(`Driver ${driver.name} responded with ${response} for event ${activeEvent.id}`);

    // Send auto-reply
    await this.sendAutoReply(driver, replyMessage);
  }

  /**
   * Send an automated reply to a driver
   */
  private async sendAutoReply(driver: any, message: string) {
    try {
      const fromNumber = await this.signalService.getLinkedNumber();
      if (!fromNumber) {
        this.logger.warn('No Signal account linked, cannot send auto-reply');
        return;
      }

      const toNumber = this.signalService.formatPhoneNumber(driver.phone);
      await this.signalService.sendMessage(fromNumber, toNumber, message);

      // Store the outbound message
      await this.prisma.signalMessage.create({
        data: {
          driverId: driver.id,
          direction: MessageDirection.OUTBOUND,
          content: message,
          isRead: true,
        },
      });

      this.logger.log(`Auto-reply sent to driver ${driver.name}`);
    } catch (error) {
      this.logger.error(`Failed to send auto-reply to driver ${driver.name}:`, error);
    }
  }

  /**
   * Mark messages as read for a driver
   */
  async markMessagesAsRead(driverId: string) {
    return this.prisma.signalMessage.updateMany({
      where: {
        driverId,
        direction: MessageDirection.INBOUND,
        isRead: false,
      },
      data: { isRead: true },
    });
  }

  /**
   * Get unread message count per driver
   */
  async getUnreadCounts() {
    const result = await this.prisma.signalMessage.groupBy({
      by: ['driverId'],
      where: {
        direction: MessageDirection.INBOUND,
        isRead: false,
      },
      _count: true,
    });

    return result.reduce((acc, item) => {
      acc[item.driverId] = item._count;
      return acc;
    }, {} as Record<string, number>);
  }

  /**
   * Get unread count for a specific driver
   */
  async getUnreadCountForDriver(driverId: string) {
    return this.prisma.signalMessage.count({
      where: {
        driverId,
        direction: MessageDirection.INBOUND,
        isRead: false,
      },
    });
  }

  /**
   * Normalize phone number for database searching
   */
  private normalizePhoneForSearch(phone: string): string {
    return phone.replace(/\D/g, '');
  }

  /**
   * Export all messages as formatted text
   */
  async exportAllMessages(): Promise<string> {
    const messages = await this.prisma.signalMessage.findMany({
      include: {
        driver: {
          select: { id: true, name: true, phone: true },
        },
      },
      orderBy: [
        { driverId: 'asc' },
        { timestamp: 'asc' },
      ],
    });

    if (messages.length === 0) {
      return 'No messages to export.';
    }

    // Group messages by driver
    const byDriver: Record<string, typeof messages> = {};
    for (const msg of messages) {
      const driverId = msg.driverId;
      if (!byDriver[driverId]) {
        byDriver[driverId] = [];
      }
      byDriver[driverId].push(msg);
    }

    // Format output
    const lines: string[] = [];
    lines.push('='.repeat(60));
    lines.push('SIGNAL CHAT EXPORT');
    lines.push(`Exported: ${new Date().toISOString()}`);
    lines.push(`Total Messages: ${messages.length}`);
    lines.push('='.repeat(60));
    lines.push('');

    for (const [driverId, driverMessages] of Object.entries(byDriver)) {
      const driver = driverMessages[0]?.driver;
      lines.push('-'.repeat(60));
      lines.push(`DRIVER: ${driver?.name || 'Unknown'}`);
      lines.push(`Phone: ${driver?.phone || 'N/A'}`);
      lines.push(`Messages: ${driverMessages.length}`);
      lines.push('-'.repeat(60));

      for (const msg of driverMessages) {
        const direction = msg.direction === 'INBOUND' ? '← IN ' : '→ OUT';
        const time = new Date(msg.timestamp).toLocaleString();
        lines.push(`[${time}] ${direction}: ${msg.content}`);
      }
      lines.push('');
    }

    return lines.join('\n');
  }

  /**
   * Delete all messages
   */
  async deleteAllMessages(): Promise<number> {
    const result = await this.prisma.signalMessage.deleteMany({});
    this.logger.log(`Deleted ${result.count} messages`);
    return result.count;
  }

  /**
   * Check which driver-event pairs have driver responses since the event started
   * @param pairs Array of {driverId, eventId, sinceTime}
   * @returns Set of eventIds where the driver has responded since sinceTime
   */
  async checkDriverResponsesSince(
    pairs: Array<{ driverId: string; eventId: string; sinceTime: Date }>,
  ): Promise<string[]> {
    const respondedEventIds: string[] = [];

    for (const pair of pairs) {
      const hasResponse = await this.prisma.signalMessage.findFirst({
        where: {
          driverId: pair.driverId,
          direction: MessageDirection.INBOUND,
          timestamp: { gte: pair.sinceTime },
        },
      });

      if (hasResponse) {
        respondedEventIds.push(pair.eventId);
      }
    }

    return respondedEventIds;
  }

  /**
   * Get message statistics
   */
  async getMessageStats() {
    const [total, inbound, outbound, unread] = await Promise.all([
      this.prisma.signalMessage.count(),
      this.prisma.signalMessage.count({
        where: { direction: MessageDirection.INBOUND },
      }),
      this.prisma.signalMessage.count({
        where: { direction: MessageDirection.OUTBOUND },
      }),
      this.prisma.signalMessage.count({
        where: { direction: MessageDirection.INBOUND, isRead: false },
      }),
    ]);

    const driversWithMessages = await this.prisma.signalMessage.groupBy({
      by: ['driverId'],
    });

    return {
      total,
      inbound,
      outbound,
      unread,
      driversWithMessages: driversWithMessages.length,
    };
  }
}
