import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { SignalService } from '../signal/signal.service';
import * as ics from 'ics';
import * as PDFDocument from 'pdfkit';

interface ScheduleEventWithDetails {
  id: string;
  title: string;
  startTime: Date;
  endTime: Date;
  pickupLocation: string | null;
  dropoffLocation: string | null;
  location: string | null;
  notes: string | null;
  type: string;
  status: string;
  vipIds: string[];
  vipNames: string[];
  vehicle: { name: string; licensePlate: string | null } | null;
}

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

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

  /**
   * Get a driver's schedule for a specific date
   */
  async getDriverSchedule(
    driverId: string,
    date: Date,
  ): Promise<ScheduleEventWithDetails[]> {
    const startOfDay = new Date(date);
    startOfDay.setHours(0, 0, 0, 0);

    const endOfDay = new Date(date);
    endOfDay.setHours(23, 59, 59, 999);

    const events = await this.prisma.scheduleEvent.findMany({
      where: {
        driverId,
        deletedAt: null,
        startTime: {
          gte: startOfDay,
          lte: endOfDay,
        },
        status: {
          not: 'CANCELLED',
        },
      },
      include: {
        vehicle: {
          select: { name: true, licensePlate: true },
        },
      },
      orderBy: { startTime: 'asc' },
    });

    return this.mapEventsWithVipNames(events);
  }

  /**
   * Get a driver's full upcoming schedule (all future events)
   */
  async getDriverFullSchedule(
    driverId: string,
  ): Promise<ScheduleEventWithDetails[]> {
    const now = new Date();
    now.setHours(0, 0, 0, 0); // Start of today

    const events = await this.prisma.scheduleEvent.findMany({
      where: {
        driverId,
        deletedAt: null,
        endTime: {
          gte: now, // Include events that haven't ended yet
        },
        status: {
          not: 'CANCELLED',
        },
      },
      include: {
        vehicle: {
          select: { name: true, licensePlate: true },
        },
      },
      orderBy: { startTime: 'asc' },
    });

    return this.mapEventsWithVipNames(events);
  }

  /**
   * Helper to map events with VIP names
   */
  private async mapEventsWithVipNames(
    events: any[],
  ): Promise<ScheduleEventWithDetails[]> {
    // Fetch VIP names for all events
    const allVipIds = [...new Set(events.flatMap((e) => e.vipIds))];
    const vips = await this.prisma.vIP.findMany({
      where: { id: { in: allVipIds } },
      select: { id: true, name: true },
    });
    const vipMap = new Map(vips.map((v) => [v.id, v.name]));

    // Map events with VIP names
    return events.map((event) => ({
      id: event.id,
      title: event.title,
      startTime: event.startTime,
      endTime: event.endTime,
      pickupLocation: event.pickupLocation,
      dropoffLocation: event.dropoffLocation,
      location: event.location,
      notes: event.notes,
      type: event.type,
      status: event.status,
      vipIds: event.vipIds,
      vipNames: event.vipIds.map((id: string) => vipMap.get(id) || 'Unknown VIP'),
      vehicle: event.vehicle,
    }));
  }

  /**
   * Generate ICS calendar file for a driver's schedule
   * @param fullSchedule If true, includes all upcoming events. If false, only the specified date.
   */
  async generateICS(driverId: string, date: Date, fullSchedule = false): Promise<string> {
    const driver = await this.prisma.driver.findFirst({
      where: { id: driverId, deletedAt: null },
    });

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

    const events = fullSchedule
      ? await this.getDriverFullSchedule(driverId)
      : await this.getDriverSchedule(driverId, date);

    if (events.length === 0) {
      throw new NotFoundException(fullSchedule ? 'No upcoming events scheduled' : 'No events scheduled for this date');
    }

    const icsEvents: ics.EventAttributes[] = events.map((event) => {
      const start = new Date(event.startTime);
      const end = new Date(event.endTime);

      const vipNames = event.vipNames.join(', ');
      const location =
        event.pickupLocation && event.dropoffLocation
          ? `${event.pickupLocation} → ${event.dropoffLocation}`
          : event.location || 'TBD';

      let description = `VIP: ${vipNames}\n`;
      if (event.vehicle) {
        description += `Vehicle: ${event.vehicle.name}`;
        if (event.vehicle.licensePlate) {
          description += ` (${event.vehicle.licensePlate})`;
        }
        description += '\n';
      }
      if (event.notes) {
        description += `Notes: ${event.notes}\n`;
      }

      return {
        start: [
          start.getFullYear(),
          start.getMonth() + 1,
          start.getDate(),
          start.getHours(),
          start.getMinutes(),
        ] as [number, number, number, number, number],
        end: [
          end.getFullYear(),
          end.getMonth() + 1,
          end.getDate(),
          end.getHours(),
          end.getMinutes(),
        ] as [number, number, number, number, number],
        title: `${event.title} - ${vipNames}`,
        description,
        location,
        status: 'CONFIRMED' as const,
        busyStatus: 'BUSY' as const,
        organizer: { name: 'VIP Coordinator', email: 'noreply@vipcoordinator.app' },
      };
    });

    const { error, value } = ics.createEvents(icsEvents);

    if (error) {
      this.logger.error('Failed to generate ICS:', error);
      throw new Error('Failed to generate calendar file');
    }

    return value || '';
  }

  /**
   * Generate PDF schedule for a driver
   * @param fullSchedule If true, includes all upcoming events. If false, only the specified date.
   */
  async generatePDF(driverId: string, date: Date, fullSchedule = false): Promise<Buffer> {
    const driver = await this.prisma.driver.findFirst({
      where: { id: driverId, deletedAt: null },
    });

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

    const events = fullSchedule
      ? await this.getDriverFullSchedule(driverId)
      : await this.getDriverSchedule(driverId, date);

    if (events.length === 0) {
      throw new NotFoundException(fullSchedule ? 'No upcoming events scheduled' : 'No events scheduled for this date');
    }

    return new Promise((resolve, reject) => {
      const chunks: Buffer[] = [];
      const doc = new PDFDocument({ margin: 50, size: 'LETTER' });

      doc.on('data', (chunk) => chunks.push(chunk));
      doc.on('end', () => resolve(Buffer.concat(chunks)));
      doc.on('error', reject);

      const dateStr = fullSchedule
        ? 'Full Upcoming Schedule'
        : date.toLocaleDateString('en-US', {
            weekday: 'long',
            year: 'numeric',
            month: 'long',
            day: 'numeric',
          });

      // Header
      doc
        .fontSize(24)
        .font('Helvetica-Bold')
        .text('VIP Coordinator', { align: 'center' });
      doc.moveDown(0.5);
      doc
        .fontSize(16)
        .font('Helvetica')
        .text(`Driver Schedule: ${driver.name}`, { align: 'center' });
      doc.fontSize(12).text(dateStr, { align: 'center' });
      doc.moveDown(1);

      // Divider line
      doc
        .moveTo(50, doc.y)
        .lineTo(doc.page.width - 50, doc.y)
        .stroke();
      doc.moveDown(1);

      // Events
      events.forEach((event, index) => {
        const startTime = new Date(event.startTime).toLocaleTimeString('en-US', {
          hour: '2-digit',
          minute: '2-digit',
        });
        const endTime = new Date(event.endTime).toLocaleTimeString('en-US', {
          hour: '2-digit',
          minute: '2-digit',
        });
        const vipNames = event.vipNames.join(', ');

        // Event header with time
        doc
          .fontSize(14)
          .font('Helvetica-Bold')
          .text(`${startTime} - ${endTime}`, { continued: false });

        // Event title
        doc.fontSize(12).font('Helvetica-Bold').text(event.title);

        // VIP
        doc.fontSize(11).font('Helvetica').text(`VIP: ${vipNames}`);

        // Location
        if (event.pickupLocation && event.dropoffLocation) {
          doc.text(`Pickup: ${event.pickupLocation}`);
          doc.text(`Dropoff: ${event.dropoffLocation}`);
        } else if (event.location) {
          doc.text(`Location: ${event.location}`);
        }

        // Vehicle
        if (event.vehicle) {
          let vehicleText = `Vehicle: ${event.vehicle.name}`;
          if (event.vehicle.licensePlate) {
            vehicleText += ` (${event.vehicle.licensePlate})`;
          }
          doc.text(vehicleText);
        }

        // Notes
        if (event.notes) {
          doc
            .fontSize(10)
            .fillColor('#666666')
            .text(`Notes: ${event.notes}`)
            .fillColor('#000000');
        }

        // Status badge
        doc
          .fontSize(9)
          .fillColor(event.status === 'COMPLETED' ? '#22c55e' : '#3b82f6')
          .text(`Status: ${event.status}`)
          .fillColor('#000000');

        // Spacing between events
        if (index < events.length - 1) {
          doc.moveDown(0.5);
          doc
            .moveTo(50, doc.y)
            .lineTo(doc.page.width - 50, doc.y)
            .strokeColor('#cccccc')
            .stroke()
            .strokeColor('#000000');
          doc.moveDown(0.5);
        }
      });

      // Footer
      doc.moveDown(2);
      doc
        .fontSize(9)
        .fillColor('#999999')
        .text(
          `Generated on ${new Date().toLocaleString('en-US')} by VIP Coordinator`,
          { align: 'center' },
        );

      doc.end();
    });
  }

  /**
   * Send schedule to driver via Signal
   * @param fullSchedule If true, sends all upcoming events. If false, only the specified date.
   */
  async sendScheduleToDriver(
    driverId: string,
    date: Date,
    format: 'ics' | 'pdf' | 'both' = 'both',
    fullSchedule = false,
  ): Promise<{ success: boolean; message: string }> {
    const driver = await this.prisma.driver.findFirst({
      where: { id: driverId, deletedAt: null },
    });

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

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

    const fromNumber = await this.signalService.getLinkedNumber();
    if (!fromNumber) {
      throw new Error('No Signal account linked');
    }

    const toNumber = this.signalService.formatPhoneNumber(driver.phone);
    const dateStr = fullSchedule
      ? 'your full upcoming schedule'
      : date.toLocaleDateString('en-US', {
          weekday: 'short',
          month: 'short',
          day: 'numeric',
        });

    const results: string[] = [];

    // Send text message first
    const events = fullSchedule
      ? await this.getDriverFullSchedule(driverId)
      : await this.getDriverSchedule(driverId, date);

    if (events.length === 0) {
      await this.signalService.sendMessage(
        fromNumber,
        toNumber,
        fullSchedule ? 'No upcoming events scheduled.' : `No events scheduled for ${dateStr}.`,
      );
      return { success: true, message: 'No events to send' };
    }

    await this.signalService.sendMessage(
      fromNumber,
      toNumber,
      `Your ${fullSchedule ? 'full upcoming' : ''} schedule${fullSchedule ? '' : ` for ${dateStr}`} (${events.length} event${events.length > 1 ? 's' : ''}):`,
    );

    // Send ICS
    if (format === 'ics' || format === 'both') {
      try {
        const icsContent = await this.generateICS(driverId, date, fullSchedule);
        const icsBase64 = Buffer.from(icsContent).toString('base64');
        const filename = fullSchedule
          ? `full-schedule-${new Date().toISOString().split('T')[0]}.ics`
          : `schedule-${date.toISOString().split('T')[0]}.ics`;

        await this.signalService.sendMessageWithAttachment(
          fromNumber,
          toNumber,
          'Calendar file - add to your calendar app:',
          icsBase64,
          filename,
          'text/calendar',
        );
        results.push('ICS');
        this.logger.log(`ICS sent to driver ${driver.name}`);
      } catch (error: any) {
        this.logger.error(`Failed to send ICS: ${error.message}`);
      }
    }

    // Send PDF
    if (format === 'pdf' || format === 'both') {
      try {
        const pdfBuffer = await this.generatePDF(driverId, date, fullSchedule);
        const pdfBase64 = pdfBuffer.toString('base64');
        const filename = fullSchedule
          ? `full-schedule-${new Date().toISOString().split('T')[0]}.pdf`
          : `schedule-${date.toISOString().split('T')[0]}.pdf`;

        await this.signalService.sendMessageWithAttachment(
          fromNumber,
          toNumber,
          fullSchedule ? 'Full schedule PDF:' : 'PDF schedule:',
          pdfBase64,
          filename,
          'application/pdf',
        );
        results.push('PDF');
        this.logger.log(`PDF sent to driver ${driver.name}`);
      } catch (error: any) {
        this.logger.error(`Failed to send PDF: ${error.message}`);
      }
    }

    if (results.length === 0) {
      throw new Error('Failed to send any schedule files');
    }

    return {
      success: true,
      message: `Sent ${results.join(' and ')} schedule to ${driver.name}`,
    };
  }
}
