/**
 * @ai-summary Notifications service with core business logic
 * @ai-context Manages notification summary, due items, and email sending
 */

import { NotificationsRepository } from '../data/notifications.repository';
import { TemplateService } from './template.service';
import { EmailService } from './email.service';
import type {
  NotificationSummary,
  DueMaintenanceItem,
  ExpiringDocument,
  EmailTemplate,
  TemplateKey,
  UserNotification,
  UnreadNotificationCount
} from './notifications.types';

export class NotificationsService {
  private repository: NotificationsRepository;
  private templateService: TemplateService;
  private emailService: EmailService;

  constructor(
    repository?: NotificationsRepository,
    templateService?: TemplateService,
    emailService?: EmailService
  ) {
    this.repository = repository || new NotificationsRepository();
    this.templateService = templateService || new TemplateService();
    this.emailService = emailService || new EmailService();
  }

  // ========================
  // Summary and Queries
  // ========================

  async getNotificationSummary(userId: string): Promise<NotificationSummary> {
    return this.repository.getNotificationSummary(userId);
  }

  async getDueMaintenanceItems(userId: string): Promise<DueMaintenanceItem[]> {
    return this.repository.getDueMaintenanceItems(userId);
  }

  async getExpiringDocuments(userId: string): Promise<ExpiringDocument[]> {
    return this.repository.getExpiringDocuments(userId);
  }

  // ========================
  // In-App Notifications
  // ========================

  async getUserNotifications(userId: string, limit?: number, includeRead?: boolean): Promise<UserNotification[]> {
    return this.repository.findUserNotifications(userId, limit, includeRead);
  }

  async getUnreadCount(userId: string): Promise<UnreadNotificationCount> {
    return this.repository.getUnreadCount(userId);
  }

  async markAsRead(userId: string, notificationId: string): Promise<UserNotification | null> {
    return this.repository.markAsRead(notificationId, userId);
  }

  async markAllAsRead(userId: string): Promise<number> {
    return this.repository.markAllAsRead(userId);
  }

  async deleteNotification(notificationId: string, userId: string): Promise<void> {
    return this.repository.deleteUserNotification(notificationId, userId);
  }

  // ========================
  // Email Templates
  // ========================

  async listTemplates(): Promise<EmailTemplate[]> {
    return this.repository.getEmailTemplates();
  }

  async getTemplate(key: TemplateKey): Promise<EmailTemplate | null> {
    return this.repository.getEmailTemplateByKey(key);
  }

  async updateTemplate(
    key: TemplateKey,
    updates: { subject?: string; body?: string; isActive?: boolean }
  ): Promise<EmailTemplate | null> {
    return this.repository.updateEmailTemplate(key, updates);
  }

  async previewTemplate(
    subject: string,
    body: string,
    variables: Record<string, string | number | boolean | null | undefined>
  ): Promise<{ subject: string; body: string; html: string }> {
    const renderedSubject = this.templateService.render(subject, variables);
    const renderedBody = this.templateService.render(body, variables);
    const renderedHtml = this.templateService.renderEmailHtml(body, variables);

    return {
      subject: renderedSubject,
      body: renderedBody,
      html: renderedHtml,
    };
  }

  // ========================
  // Email Sending
  // ========================

  async sendMaintenanceNotification(
    userId: string,
    userEmail: string,
    userName: string,
    item: DueMaintenanceItem
  ): Promise<void> {
    const templateKey: TemplateKey = item.isOverdue
      ? 'maintenance_overdue'
      : 'maintenance_due_soon';

    const template = await this.repository.getEmailTemplateByKey(templateKey);
    if (!template || !template.isActive) {
      throw new Error(`Template ${templateKey} not found or inactive`);
    }

    const variables = {
      userName,
      vehicleName: item.vehicleName,
      category: item.category,
      subtypes: item.subtypes.join(', '),
      dueDate: item.nextDueDate || 'N/A',
      dueMileage: item.nextDueMileage?.toString() || 'N/A',
    };

    const subject = this.templateService.render(template.subject, variables);
    const htmlBody = this.templateService.renderEmailHtml(template.body, variables);

    try {
      await this.emailService.send(userEmail, subject, htmlBody);

      await this.repository.insertNotificationLog({
        user_id: userId,
        notification_type: 'email',
        template_key: templateKey,
        recipient_email: userEmail,
        subject,
        reference_type: 'maintenance_schedule',
        reference_id: item.id,
        status: 'sent',
      });
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';

      await this.repository.insertNotificationLog({
        user_id: userId,
        notification_type: 'email',
        template_key: templateKey,
        recipient_email: userEmail,
        subject,
        reference_type: 'maintenance_schedule',
        reference_id: item.id,
        status: 'failed',
        error_message: errorMessage,
      });

      throw error;
    }
  }

  async sendDocumentNotification(
    userId: string,
    userEmail: string,
    userName: string,
    document: ExpiringDocument
  ): Promise<void> {
    const templateKey: TemplateKey = document.isExpired
      ? 'document_expired'
      : 'document_expiring';

    const template = await this.repository.getEmailTemplateByKey(templateKey);
    if (!template || !template.isActive) {
      throw new Error(`Template ${templateKey} not found or inactive`);
    }

    const variables = {
      userName,
      vehicleName: document.vehicleName,
      documentType: document.documentType,
      documentTitle: document.title,
      expirationDate: document.expirationDate || 'N/A',
    };

    const subject = this.templateService.render(template.subject, variables);
    const htmlBody = this.templateService.renderEmailHtml(template.body, variables);

    try {
      await this.emailService.send(userEmail, subject, htmlBody);

      await this.repository.insertNotificationLog({
        user_id: userId,
        notification_type: 'email',
        template_key: templateKey,
        recipient_email: userEmail,
        subject,
        reference_type: 'document',
        reference_id: document.id,
        status: 'sent',
      });
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';

      await this.repository.insertNotificationLog({
        user_id: userId,
        notification_type: 'email',
        template_key: templateKey,
        recipient_email: userEmail,
        subject,
        reference_type: 'document',
        reference_id: document.id,
        status: 'failed',
        error_message: errorMessage,
      });

      throw error;
    }
  }

  /**
   * Send tier change notification (email + in-app) when subscription tier changes
   * @param userId User ID
   * @param userEmail User email address
   * @param userName User display name
   * @param previousTier Previous subscription tier
   * @param newTier New subscription tier
   * @param reason Reason for change (admin_override, user_upgrade, user_downgrade, grace_period_expiration)
   */
  async sendTierChangeNotification(
    userId: string,
    userEmail: string,
    userName: string,
    previousTier: string,
    newTier: string,
    reason: 'admin_override' | 'user_upgrade' | 'user_downgrade' | 'grace_period_expiration'
  ): Promise<void> {
    const templateKey: TemplateKey = 'subscription_tier_change';

    const template = await this.repository.getEmailTemplateByKey(templateKey);
    if (!template || !template.isActive) {
      // Log but don't throw - notifications are non-critical
      console.warn(`Template ${templateKey} not found or inactive, skipping tier change notification`);
      return;
    }

    const isDowngrade = this.getTierRank(newTier) < this.getTierRank(previousTier);
    const isUpgrade = this.getTierRank(newTier) > this.getTierRank(previousTier);
    const changeType = isUpgrade ? 'Upgraded' : isDowngrade ? 'Downgraded' : 'Changed';

    const reasonDisplayMap: Record<string, string> = {
      admin_override: 'Administrator adjustment',
      user_upgrade: 'Subscription upgrade',
      user_downgrade: 'Subscription downgrade',
      grace_period_expiration: 'Payment grace period expired',
    };

    const vehicleLimitMap: Record<string, string> = {
      free: '2',
      pro: '5',
      enterprise: 'unlimited',
    };

    // Build additional info based on change type
    let additionalInfo = '';
    if (isDowngrade) {
      const vehicleLimit = vehicleLimitMap[newTier.toLowerCase()] || '2';
      additionalInfo = `As a result of this change, you now have access to ${vehicleLimit} vehicles. Any vehicles beyond this limit will be hidden but your data remains safe.`;
    } else if (isUpgrade) {
      additionalInfo = `You now have access to all the features included in the ${this.formatTierName(newTier)} tier. Enjoy your enhanced MotoVaultPro experience!`;
    }

    const variables = {
      userName,
      changeType,
      previousTier: this.formatTierName(previousTier),
      newTier: this.formatTierName(newTier),
      reason: reasonDisplayMap[reason] || reason,
      additionalInfo,
    };

    const subject = this.templateService.render(template.subject, variables);
    const htmlBody = this.templateService.renderEmailHtml(template.body, variables);

    // Send email notification
    try {
      await this.emailService.send(userEmail, subject, htmlBody);

      await this.repository.insertNotificationLog({
        user_id: userId,
        notification_type: 'email',
        template_key: templateKey,
        recipient_email: userEmail,
        subject,
        reference_type: 'subscription',
        status: 'sent',
      });
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      console.error(`Failed to send tier change email to ${userEmail}: ${errorMessage}`);

      await this.repository.insertNotificationLog({
        user_id: userId,
        notification_type: 'email',
        template_key: templateKey,
        recipient_email: userEmail,
        subject,
        reference_type: 'subscription',
        status: 'failed',
        error_message: errorMessage,
      });
    }

    // Create in-app notification
    try {
      const title = `Subscription ${changeType}`;
      const message = `Your subscription has been ${changeType.toLowerCase()} from ${this.formatTierName(previousTier)} to ${this.formatTierName(newTier)}. ${reasonDisplayMap[reason]}.`;

      await this.repository.insertUserNotification({
        userId,
        notificationType: 'subscription',
        title,
        message,
        referenceType: 'subscription',
      });
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      console.error(`Failed to create in-app tier change notification for ${userId}: ${errorMessage}`);
    }
  }

  /**
   * Get numeric rank for tier comparison
   */
  private getTierRank(tier: string): number {
    const ranks: Record<string, number> = {
      free: 1,
      pro: 2,
      enterprise: 3,
    };
    return ranks[tier.toLowerCase()] || 0;
  }

  /**
   * Format tier name for display
   */
  private formatTierName(tier: string): string {
    const names: Record<string, string> = {
      free: 'Free',
      pro: 'Pro',
      enterprise: 'Enterprise',
    };
    return names[tier.toLowerCase()] || tier;
  }

  /**
   * Send a test email for a template to verify email configuration
   * @param key Template key to test
   * @param recipientEmail Email address to send test to
   * @param recipientName Name to use in template
   * @returns Rendered subject and body that was sent
   */
  async sendTestEmail(
    key: TemplateKey,
    recipientEmail: string,
    recipientName: string
  ): Promise<{ subject: string; body: string; success: boolean; error?: string }> {
    const template = await this.repository.getEmailTemplateByKey(key);
    if (!template) {
      return {
        subject: '',
        body: '',
        success: false,
        error: `Template '${key}' not found`
      };
    }

    // Sample variables based on template type
    const sampleVariables = this.getSampleVariables(key, recipientName);

    const subject = this.templateService.render(template.subject, sampleVariables);
    const body = this.templateService.render(template.body, sampleVariables);
    const htmlBody = this.templateService.renderEmailHtml(template.body, sampleVariables);

    try {
      await this.emailService.send(recipientEmail, `[TEST] ${subject}`, htmlBody);

      return {
        subject,
        body,
        success: true
      };
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : 'Unknown error';
      return {
        subject,
        body,
        success: false,
        error: errorMessage
      };
    }
  }

  /**
   * Get sample variables for a template based on its type
   */
  private getSampleVariables(key: TemplateKey, userName: string): Record<string, string> {
    const baseVariables = { userName };

    switch (key) {
      case 'maintenance_due_soon':
      case 'maintenance_overdue':
        return {
          ...baseVariables,
          vehicleName: '2024 Toyota Camry',
          category: 'Routine Maintenance',
          subtypes: 'Oil Change, Air Filter',
          dueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toLocaleDateString(),
          dueMileage: '50,000',
        };
      case 'document_expiring':
      case 'document_expired':
        return {
          ...baseVariables,
          vehicleName: '2024 Toyota Camry',
          documentType: 'Insurance',
          documentTitle: 'State Farm Auto Policy',
          expirationDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000).toLocaleDateString(),
        };
      case 'subscription_tier_change':
        return {
          ...baseVariables,
          changeType: 'Upgraded',
          previousTier: 'Free',
          newTier: 'Pro',
          reason: 'Subscription upgrade',
          additionalInfo: 'You now have access to all the features included in the Pro tier. Enjoy your enhanced MotoVaultPro experience!',
        };
      case 'receipt_processed':
        return {
          ...baseVariables,
          vehicleName: '2024 Toyota Camry',
          recordType: 'Fuel Log',
          merchantName: 'Shell Gas Station',
          totalAmount: '45.50',
          date: new Date().toLocaleDateString(),
        };
      case 'receipt_failed':
        return {
          ...baseVariables,
          errorReason: 'Unable to extract receipt data from the attached image.',
          guidance: 'Please try again with a clearer image or PDF. Tips: use a well-lit, high-contrast photo; ensure text is legible and not cut off; PDF receipts work best.',
        };
      case 'receipt_pending_vehicle':
        return {
          ...baseVariables,
          recordType: 'Maintenance Record',
          merchantName: 'AutoZone',
          totalAmount: '89.99',
          date: new Date().toLocaleDateString(),
        };
      default:
        return baseVariables;
    }
  }

  private async createInAppNotification(
    userId: string,
    item: DueMaintenanceItem,
    _reminderLevel: 1 | 2 | 3
  ): Promise<void> {
    const title = item.isOverdue
      ? `OVERDUE: ${this.getCategoryDisplayName(item.category)} for ${item.vehicleName}`
      : `Due Soon: ${this.getCategoryDisplayName(item.category)} for ${item.vehicleName}`;

    const message = item.isOverdue
      ? `Your ${item.subtypes.join(', ')} maintenance was due ${item.nextDueDate || `at ${item.nextDueMileage?.toLocaleString()} miles`}`
      : `Your ${item.subtypes.join(', ')} maintenance is due ${item.nextDueDate || `at ${item.nextDueMileage?.toLocaleString()} miles`}`;

    await this.repository.insertUserNotification({
      userId,
      notificationType: 'maintenance',
      title,
      message,
      referenceType: 'maintenance_schedule',
      referenceId: item.id,
      vehicleId: item.vehicleId,
    });
  }

  private getCategoryDisplayName(category: string): string {
    const names: Record<string, string> = {
      routine_maintenance: 'Routine Maintenance',
      repair: 'Repair',
      performance_upgrade: 'Performance Upgrade',
    };
    return names[category] || category;
  }

  /**
   * Check if a reminder should be sent based on the due date and reminder days
   */
  private shouldSendReminder(item: DueMaintenanceItem, reminderDays: number, today: Date): boolean {
    if (!item.nextDueDate) return false;

    const dueDate = new Date(item.nextDueDate);
    const diffTime = dueDate.getTime() - today.getTime();
    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));

    // Send notification if today is exactly reminderDays before due date
    return diffDays === reminderDays;
  }

  /**
   * Process all pending notifications (called by scheduled job)
   * Checks all active schedules and sends notifications based on reminder_days settings
   */
  async processNotifications(): Promise<{
    processed: number;
    emailsSent: number;
    inAppCreated: number;
    errors: string[];
  }> {
    const results = { processed: 0, emailsSent: 0, inAppCreated: 0, errors: [] as string[] };
    const today = new Date();
    const todayStr = today.toISOString().split('T')[0];

    // Get all users with active schedules that have reminders configured
    const usersWithSchedules = await this.repository.getUsersWithActiveSchedules();

    for (const userData of usersWithSchedules) {
      const { userId, userEmail, userName } = userData;

      try {
        // Get due maintenance items for this user
        const dueItems = await this.repository.getDueMaintenanceItems(userId);

        for (const item of dueItems) {
          // Check each reminder level (1, 2, 3) for this schedule
          const reminderLevels: Array<{ level: 1 | 2 | 3; days: number | null | undefined }> = [
            { level: 1, days: item.reminderDays1 },
            { level: 2, days: item.reminderDays2 },
            { level: 3, days: item.reminderDays3 },
          ];

          for (const { level, days } of reminderLevels) {
            if (!days) continue; // Skip if reminder not configured

            // Calculate if today matches the reminder day
            const shouldNotify = this.shouldSendReminder(item, days, today);
            if (!shouldNotify && !item.isOverdue) continue;

            // Check if we've already sent this notification today
            const alreadySent = await this.repository.hasNotificationBeenSent(
              item.id,
              todayStr,
              level
            );

            if (alreadySent) continue;

            results.processed++;

            try {
              // Create in-app notification
              await this.createInAppNotification(userId, item, level);
              results.inAppCreated++;

              // Send email if enabled
              if (item.emailNotifications) {
                await this.sendMaintenanceNotification(userId, userEmail, userName, item);
                results.emailsSent++;
              }

              // Record that notification was sent
              await this.repository.recordSentNotification({
                scheduleId: item.id,
                notificationDate: todayStr,
                reminderLevel: level,
                deliveryMethod: item.emailNotifications ? 'both' : 'in_app',
              });
            } catch (error) {
              const errorMsg = error instanceof Error ? error.message : String(error);
              results.errors.push(`Failed for schedule ${item.id} (level ${level}): ${errorMsg}`);
            }
          }
        }
      } catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        results.errors.push(`Failed processing user ${userId}: ${errorMsg}`);
      }
    }

    return results;
  }
}
