import {type NtfyNotification, NtfyPriority, NtfyTag} from '@plunk/types';
import signale from 'signale';

/**
 * Service for sending notifications via ntfy.sh
 * Supports configurable ntfy server URL via NTFY_URL environment variable
 */
export class NtfyService {
  private static ntfyUrl: string | null = null;
  private static isConfigured = false;

  /**
   * Send a notification to ntfy
   * @param notification - The notification details
   * @returns Promise<void>
   */
  public static async send(notification: NtfyNotification): Promise<void> {
    this.initialize();

    if (!this.ntfyUrl) {
      // Silently skip if not configured
      return;
    }

    try {
      const headers: Record<string, string> = {
        'Content-Type': 'text/plain',
        'Title': notification.title,
      };

      if (notification.priority) {
        headers.Priority = notification.priority.toString();
      }

      if (notification.tags && notification.tags.length > 0) {
        headers.Tags = notification.tags.join(',');
      }

      const response = await fetch(this.ntfyUrl, {
        method: 'POST',
        headers,
        body: notification.message,
      });

      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }

      signale.success(`[NTFY] Sent notification: ${notification.title}`);
    } catch (error) {
      signale.error('[NTFY] Failed to send notification:', error);
      // Don't throw - notifications should not break the main flow
    }
  }

  /**
   * Send a high-priority notification
   */
  public static async sendHigh(title: string, message: string, tags?: NtfyTag[]): Promise<void> {
    await this.send({
      title,
      message,
      priority: NtfyPriority.HIGH,
      tags,
    });
  }

  /**
   * Send a max-priority urgent notification
   */
  public static async sendUrgent(title: string, message: string, tags?: NtfyTag[]): Promise<void> {
    await this.send({
      title,
      message,
      priority: NtfyPriority.MAX,
      tags,
    });
  }

  /**
   * Send a low-priority informational notification
   */
  public static async sendInfo(title: string, message: string, tags?: NtfyTag[]): Promise<void> {
    await this.send({
      title,
      message,
      priority: NtfyPriority.LOW,
      tags,
    });
  }

  /**
   * Send a default priority notification
   */
  public static async sendDefault(title: string, message: string, tags?: NtfyTag[]): Promise<void> {
    await this.send({
      title,
      message,
      priority: NtfyPriority.DEFAULT,
      tags,
    });
  }

  /**
   * Notify about a new project creation
   */
  public static async notifyProjectCreated(projectName: string, projectId: string, userId: string): Promise<void> {
    await this.sendDefault(
      'New Project Created',
      `Project "${projectName}" (${projectId}) was created by user ${userId}`,
      [NtfyTag.ROCKET, NtfyTag.SUCCESS],
    );
  }

  // ===== Event-specific notification helpers =====

  /**
   * Notify about subscription started
   */
  public static async notifySubscriptionStarted(
    projectName: string,
    projectId: string,
    subscriptionId: string,
  ): Promise<void> {
    await this.sendHigh(
      'Subscription Started',
      `Project "${projectName}" (${projectId}) started a paid subscription (${subscriptionId})`,
      [NtfyTag.MONEY, NtfyTag.SUCCESS],
    );
  }

  /**
   * Notify about subscription cancelled
   */
  public static async notifySubscriptionCancelled(
    projectName: string,
    projectId: string,
    subscriptionId: string,
  ): Promise<void> {
    await this.sendHigh(
      'Subscription Cancelled',
      `Project "${projectName}" (${projectId}) cancelled their subscription (${subscriptionId})`,
      [NtfyTag.WARNING, NtfyTag.MONEY],
    );
  }

  /**
   * Notify about project disabled due to security risk
   */
  public static async notifyProjectDisabledForSecurity(
    projectName: string,
    projectId: string,
    violations: string[],
  ): Promise<void> {
    const violationText = violations.join(', ');
    await this.sendUrgent(
      'Project Disabled - Security Risk',
      `Project "${projectName}" (${projectId}) was automatically disabled due to: ${violationText}`,
      [NtfyTag.SHIELD, NtfyTag.ERROR, NtfyTag.SKULL],
    );
  }

  /**
   * Notify about payment failure
   */
  public static async notifyPaymentFailed(projectName: string, projectId: string): Promise<void> {
    await this.sendHigh('Payment Failed', `Payment failed for project "${projectName}" (${projectId})`, [
      NtfyTag.WARNING,
      NtfyTag.MONEY,
    ]);
  }

  /**
   * Notify about project disabled due to payment failure
   */
  public static async notifyProjectDisabledForPayment(projectName: string, projectId: string): Promise<void> {
    await this.sendUrgent(
      'Project Disabled - Payment Failed',
      `Project "${projectName}" (${projectId}) was automatically disabled due to a failed recurring payment`,
      [NtfyTag.WARNING, NtfyTag.MONEY, NtfyTag.ERROR],
    );
  }

  /**
   * Notify about successful invoice payment - MIN priority (routine)
   */
  public static async notifyInvoicePaid(projectName: string, projectId: string): Promise<void> {
    await this.send({
      title: 'Invoice Paid',
      message: `Invoice paid for project "${projectName}" (${projectId})`,
      priority: NtfyPriority.MIN,
      tags: [NtfyTag.MONEY, NtfyTag.SUCCESS],
    });
  }

  /**
   * Notify about subscription update - LOW priority (minor changes)
   */
  public static async notifySubscriptionUpdated(projectName: string, projectId: string): Promise<void> {
    await this.send({
      title: 'Subscription Updated',
      message: `Subscription updated for project "${projectName}" (${projectId})`,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.MONEY, NtfyTag.INFO],
    });
  }

  /**
   * Notify about project deletion - DEFAULT priority
   */
  public static async notifyProjectDeleted(projectName: string, projectId: string, userId: string): Promise<void> {
    await this.sendDefault('Project Deleted', `Project "${projectName}" (${projectId}) was deleted by user ${userId}`, [
      NtfyTag.WARNING,
    ]);
  }

  /**
   * Notify about security warning (non-critical)
   */
  public static async notifySecurityWarning(projectName: string, projectId: string, warnings: string[]): Promise<void> {
    // Import redis at runtime to avoid circular dependencies
    const {redis} = await import('../database/redis.js');

    const cacheKey = `ntfy:security:warning:${projectId}`;
    const ttl = 3600; // 1 hour

    // Use SETNX to atomically check and set the flag (prevents race conditions)
    const wasSet = await redis.set(cacheKey, '1', 'EX', ttl, 'NX');
    if (!wasSet) {
      return;
    }

    const warningText = warnings.join(', ');
    await this.sendDefault(
      'Security Warning',
      `Project "${projectName}" (${projectId}) has security warnings: ${warningText}`,
      [NtfyTag.WARNING, NtfyTag.SHIELD],
    );
  }

  /**
   * Notify about email bounce - LOW priority (high volume)
   * Rate-limited to only send notification every 20 bounces with latest 20 bounce details
   */
  public static async notifyEmailBounce(
    projectName: string,
    projectId: string,
    recipientEmail: string,
    bounceType?: string,
  ): Promise<void> {
    // Import redis at runtime to avoid circular dependencies
    const {redis} = await import('../database/redis.js');

    // Use Redis counters to track bounce count globally
    const globalCountKey = `ntfy:bounce:count`;
    const bounceListKey = `ntfy:bounce:latest`;

    // Increment global counter
    const globalCount = await redis.incr(globalCountKey);

    // Store this bounce event in a list (keep latest 20)
    const bounceEvent = JSON.stringify({
      projectName,
      projectId,
      recipientEmail,
      bounceType: bounceType || 'Unknown',
      timestamp: new Date().toISOString(),
    });
    await redis.lpush(bounceListKey, bounceEvent);
    await redis.ltrim(bounceListKey, 0, 19); // Keep only latest 20

    // Set expiry on first increment (24 hour rolling window)
    if (globalCount === 1) {
      await redis.expire(globalCountKey, 86400);
      await redis.expire(bounceListKey, 86400);
    }

    // Only send notification every 20 bounces
    if (globalCount % 20 === 0) {
      // Get the latest 20 bounces
      const latestBounces = await redis.lrange(bounceListKey, 0, 19);

      // Parse and format the bounce events
      const bounceDetails = latestBounces
        .map(bounce => {
          try {
            const parsed = JSON.parse(bounce);
            return `  • ${parsed.recipientEmail} (${parsed.bounceType}) - ${parsed.projectName}`;
          } catch {
            return null;
          }
        })
        .filter(Boolean)
        .join('\n');

      await this.send({
        title: 'Email Bounces',
        message: `20 email bounces detected (total: ${globalCount})\n\nLatest 20 bounces:\n${bounceDetails}`,
        priority: NtfyPriority.LOW,
        tags: [NtfyTag.WARNING],
      });
    }
  }

  /**
   * Notify about email complaint - HIGH priority (reputation risk)
   */
  public static async notifyEmailComplaint(
    projectName: string,
    projectId: string,
    recipientEmail: string,
  ): Promise<void> {
    await this.sendHigh(
      'Email Complaint',
      `Email complaint received from ${recipientEmail} in project "${projectName}" (${projectId})`,
      [NtfyTag.WARNING, NtfyTag.ERROR],
    );
  }

  /**
   * Notify about new user account created via signup - LOW priority
   */
  public static async notifyUserSignup(userEmail: string, userId: string): Promise<void> {
    await this.send({
      title: 'New User Signup',
      message: `New user registered: ${userEmail} (${userId})`,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.ROCKET, NtfyTag.SUCCESS],
    });
  }

  /**
   * Notify about failed signup attempt with invalid email - LOW priority
   */
  public static async notifyFailedSignupAttempt(email: string, reasons: string[]): Promise<void> {
    const reasonText = reasons.join(', ');
    await this.send({
      title: 'Failed Signup - Invalid Email',
      message: `Signup attempt blocked for email: ${email}\nReasons: ${reasonText}`,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.WARNING, NtfyTag.SHIELD],
    });
  }

  /**
   * Notify about new user account created via OAuth - LOW priority
   */
  public static async notifyUserOAuthSignup(
    userEmail: string,
    userId: string,
    provider: 'GitHub' | 'Google',
  ): Promise<void> {
    await this.send({
      title: `New User - ${provider} OAuth`,
      message: `New user registered via ${provider}: ${userEmail} (${userId})`,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.ROCKET, NtfyTag.SUCCESS],
    });
  }

  /**
   * Notify about campaign created (draft) - LOW priority
   */
  public static async notifyCampaignCreated(
    campaignName: string,
    projectName: string,
    projectId: string,
  ): Promise<void> {
    await this.send({
      title: 'Campaign Created',
      message: `Campaign "${campaignName}" created in project "${projectName}" (${projectId})`,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.ROCKET],
    });
  }

  // ===== Campaign event notifications =====

  /**
   * Notify about campaign scheduled - DEFAULT priority
   */
  public static async notifyCampaignScheduled(
    campaignName: string,
    projectName: string,
    projectId: string,
    scheduledFor: Date,
    recipientCount: number,
  ): Promise<void> {
    await this.sendDefault(
      'Campaign Scheduled',
      `Campaign "${campaignName}" scheduled to send to ${recipientCount} recipients at ${scheduledFor.toISOString()} in project "${projectName}" (${projectId})`,
      [NtfyTag.ROCKET, NtfyTag.INFO],
    );
  }

  /**
   * Notify about campaign sending started - LOW priority (informational)
   */
  public static async notifyCampaignSendingStarted(
    campaignName: string,
    projectName: string,
    projectId: string,
    recipientCount: number,
  ): Promise<void> {
    await this.send({
      title: 'Campaign Sending Started',
      message: `Campaign "${campaignName}" started sending to ${recipientCount} recipients in project "${projectName}" (${projectId})`,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.ROCKET, NtfyTag.CHART],
    });
  }

  /**
   * Notify about campaign send completed - DEFAULT priority
   */
  public static async notifyCampaignSendCompleted(
    campaignName: string,
    projectName: string,
    projectId: string,
    recipientCount: number,
  ): Promise<void> {
    await this.sendDefault(
      'Campaign Send Completed',
      `Campaign "${campaignName}" successfully sent to ${recipientCount} recipients in project "${projectName}" (${projectId})`,
      [NtfyTag.ROCKET, NtfyTag.SUCCESS],
    );
  }

  /**
   * Notify about campaign cancelled - LOW priority
   */
  public static async notifyCampaignCancelled(
    campaignName: string,
    projectName: string,
    projectId: string,
  ): Promise<void> {
    await this.send({
      title: 'Campaign Cancelled',
      message: `Campaign "${campaignName}" was cancelled in project "${projectName}" (${projectId})`,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.WARNING],
    });
  }

  /**
   * Notify about campaign deleted - LOW priority
   */
  public static async notifyCampaignDeleted(
    campaignName: string,
    projectName: string,
    projectId: string,
  ): Promise<void> {
    await this.send({
      title: 'Campaign Deleted',
      message: `Campaign "${campaignName}" was deleted from project "${projectName}" (${projectId})`,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.INFO],
    });
  }

  /**
   * Notify about workflow created - LOW priority
   */
  public static async notifyWorkflowCreated(
    workflowName: string,
    projectName: string,
    projectId: string,
  ): Promise<void> {
    await this.send({
      title: 'Workflow Created',
      message: `Workflow "${workflowName}" created in project "${projectName}" (${projectId})`,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.ROCKET],
    });
  }

  // ===== Workflow event notifications =====

  /**
   * Notify about workflow enabled
   */
  public static async notifyWorkflowEnabled(
    workflowName: string,
    projectName: string,
    projectId: string,
  ): Promise<void> {
    await this.sendDefault(
      'Workflow Enabled',
      `Workflow "${workflowName}" is now active in project "${projectName}" (${projectId})`,
      [NtfyTag.SUCCESS],
    );
  }

  /**
   * Notify about workflow disabled
   */
  public static async notifyWorkflowDisabled(
    workflowName: string,
    projectName: string,
    projectId: string,
  ): Promise<void> {
    await this.sendDefault(
      'Workflow Disabled',
      `Workflow "${workflowName}" has been disabled in project "${projectName}" (${projectId})`,
      [NtfyTag.WARNING],
    );
  }

  /**
   * Notify about workflow deleted - LOW priority
   */
  public static async notifyWorkflowDeleted(
    workflowName: string,
    projectName: string,
    projectId: string,
  ): Promise<void> {
    await this.send({
      title: 'Workflow Deleted',
      message: `Workflow "${workflowName}" was deleted from project "${projectName}" (${projectId})`,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.INFO],
    });
  }

  /**
   * Notify about workflow execution failed
   */
  public static async notifyWorkflowExecutionFailed(
    workflowName: string,
    projectName: string,
    projectId: string,
    contactEmail: string,
    error: string,
  ): Promise<void> {
    await this.sendHigh(
      'Workflow Execution Failed',
      `Workflow "${workflowName}" failed for contact ${contactEmail} in project "${projectName}" (${projectId}). Error: ${error}`,
      [NtfyTag.ERROR, NtfyTag.WARNING],
    );
  }

  /**
   * Notify about domain added
   */
  public static async notifyDomainAdded(domain: string, projectName: string, projectId: string): Promise<void> {
    await this.sendDefault(
      'Domain Added',
      `Domain "${domain}" added for verification in project "${projectName}" (${projectId})`,
      [NtfyTag.INFO],
    );
  }

  // ===== Domain verification notifications =====

  /**
   * Notify about domain verified
   */
  public static async notifyDomainVerified(domain: string, projectName: string, projectId: string): Promise<void> {
    await this.sendDefault(
      'Domain Verified',
      `Domain "${domain}" is now verified and ready for use in project "${projectName}" (${projectId})`,
      [NtfyTag.SUCCESS, NtfyTag.SHIELD],
    );
  }

  /**
   * Notify about domain verification failed
   */
  public static async notifyDomainVerificationFailed(
    domain: string,
    projectName: string,
    projectId: string,
  ): Promise<void> {
    await this.sendHigh(
      'Domain Verification Failed',
      `Domain "${domain}" failed verification in project "${projectName}" (${projectId}). Email sending may be affected.`,
      [NtfyTag.WARNING, NtfyTag.SHIELD],
    );
  }

  /**
   * Notify about domain removed
   */
  public static async notifyDomainRemoved(domain: string, projectName: string, projectId: string): Promise<void> {
    await this.sendInfo(
      'Domain Removed',
      `Domain "${domain}" was removed from project "${projectName}" (${projectId})`,
      [NtfyTag.INFO],
    );
  }

  /**
   * Notify about billing limit approaching (80% threshold)
   */
  public static async notifyBillingLimitApproaching(
    projectName: string,
    projectId: string,
    usage: number,
    limit: number,
    percentage: number,
    sourceType: string,
  ): Promise<void> {
    // Import redis at runtime to avoid circular dependencies
    const {redis} = await import('../database/redis.js');

    const cacheKey = `ntfy:billing:warning:${projectId}:${sourceType}`;
    const ttl = 86400; // 24 hours

    // Use SETNX to atomically check and set the flag (prevents race conditions)
    const wasSet = await redis.set(cacheKey, '1', 'EX', ttl, 'NX');
    if (!wasSet) {
      return;
    }

    await this.sendDefault(
      'Billing Limit Warning',
      `Email usage at ${Math.round(percentage)}% (${usage}/${limit}) for ${sourceType} in project "${projectName}" (${projectId})`,
      [NtfyTag.WARNING, NtfyTag.MONEY, NtfyTag.CHART],
    );
  }

  // ===== Billing and usage limit notifications =====

  /**
   * Notify about billing limit exceeded - MAX priority (blocks operations)
   */
  public static async notifyBillingLimitExceeded(
    projectName: string,
    projectId: string,
    usage: number,
    limit: number,
    sourceType: string,
  ): Promise<void> {
    // Import redis at runtime to avoid circular dependencies
    const {redis} = await import('../database/redis.js');

    const cacheKey = `ntfy:billing:exceeded:${projectId}:${sourceType}`;
    const ttl = 86400; // 24 hours

    // Use SETNX to atomically check and set the flag (prevents race conditions)
    const wasSet = await redis.set(cacheKey, '1', 'EX', ttl, 'NX');
    if (!wasSet) {
      return;
    }

    await this.sendUrgent(
      'Billing Limit Exceeded',
      `Email usage limit reached (${usage}/${limit}) for ${sourceType} in project "${projectName}" (${projectId}). Further emails are blocked.`,
      [NtfyTag.ERROR, NtfyTag.MONEY, NtfyTag.SKULL],
    );
  }

  /**
   * Notify about API keys regenerated
   */
  public static async notifyApiKeysRegenerated(projectName: string, projectId: string, userId: string): Promise<void> {
    await this.sendHigh(
      'API Keys Regenerated',
      `API keys for project "${projectName}" (${projectId}) were regenerated by user ${userId}`,
      [NtfyTag.SHIELD, NtfyTag.WARNING],
    );
  }

  // ===== API key notifications =====

  /**
   * Notify about contact import started - MIN priority (routine, high volume)
   */
  public static async notifyContactImportStarted(
    projectName: string,
    projectId: string,
    filename: string,
    totalRows: number,
  ): Promise<void> {
    await this.send({
      title: 'Contact Import Started',
      message: `Importing ${totalRows} contacts from "${filename}" into project "${projectName}" (${projectId})`,
      priority: NtfyPriority.MIN,
      tags: [NtfyTag.ROCKET],
    });
  }

  // ===== Contact import notifications =====

  /**
   * Notify about contact import completed
   */
  public static async notifyContactImportCompleted(
    projectName: string,
    projectId: string,
    filename: string,
    successCount: number,
    createdCount: number,
    updatedCount: number,
    failureCount: number,
  ): Promise<void> {
    await this.sendDefault(
      'Contact Import Completed',
      `Import "${filename}" completed for project "${projectName}" (${projectId}). Success: ${successCount} (${createdCount} new, ${updatedCount} updated), Errors: ${failureCount}`,
      [NtfyTag.SUCCESS, NtfyTag.CHART],
    );
  }

  /**
   * Notify about contact import failed
   */
  public static async notifyContactImportFailed(
    projectName: string,
    projectId: string,
    filename: string,
    error: string,
  ): Promise<void> {
    await this.sendHigh(
      'Contact Import Failed',
      `Contact import "${filename}" failed for project "${projectName}" (${projectId}). Error: ${error}`,
      [NtfyTag.ERROR],
    );
  }

  /**
   * Notify about segment created - MIN priority
   */
  public static async notifySegmentCreated(segmentName: string, projectName: string, projectId: string): Promise<void> {
    await this.send({
      title: 'Segment Created',
      message: `Segment "${segmentName}" created in project "${projectName}" (${projectId})`,
      priority: NtfyPriority.MIN,
      tags: [NtfyTag.INFO],
    });
  }

  // ===== Segment notifications =====

  /**
   * Notify about segment membership computed - LOW priority
   */
  public static async notifySegmentMembershipComputed(
    segmentName: string,
    projectName: string,
    projectId: string,
    memberCount: number,
    added?: number,
    removed?: number,
  ): Promise<void> {
    let message = `Segment "${segmentName}" in project "${projectName}" (${projectId}) now has ${memberCount} members`;

    if (added !== undefined && removed !== undefined) {
      const changes: string[] = [];
      if (added > 0) changes.push(`+${added} added`);
      if (removed > 0) changes.push(`-${removed} removed`);
      if (changes.length > 0) {
        message += ` (${changes.join(', ')})`;
      }
    }

    await this.send({
      title: 'Segment Membership Updated',
      message,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.CHART],
    });
  }

  /**
   * Notify about bundled segment membership updates - LOW priority
   * Used when multiple segments are updated in a single processing cycle
   */
  public static async notifySegmentMembershipBundled(
    projectName: string,
    projectId: string,
    segmentCount: number,
    totalAdded: number,
    totalRemoved: number,
  ): Promise<void> {
    const changes: string[] = [];
    if (totalAdded > 0) changes.push(`+${totalAdded} added`);
    if (totalRemoved > 0) changes.push(`-${totalRemoved} removed`);

    const changesText = changes.length > 0 ? ` (${changes.join(', ')})` : '';
    const message = `${segmentCount} segment${segmentCount > 1 ? 's' : ''} updated in project "${projectName}" (${projectId})${changesText}`;

    await this.send({
      title: 'Segment Memberships Updated',
      message,
      priority: NtfyPriority.LOW,
      tags: [NtfyTag.CHART],
    });
  }

  /**
   * Notify about segment deleted - MIN priority
   */
  public static async notifySegmentDeleted(segmentName: string, projectName: string, projectId: string): Promise<void> {
    await this.send({
      title: 'Segment Deleted',
      message: `Segment "${segmentName}" was deleted from project "${projectName}" (${projectId})`,
      priority: NtfyPriority.MIN,
      tags: [NtfyTag.INFO],
    });
  }

  public static async notifyEarlyFraudWarning(
    chargeId: string,
    fraudType: string,
    cardFingerprint: string | null,
    customerEmail: string | null,
  ): Promise<void> {
    const details = [
      `Charge: ${chargeId}`,
      `Fraud type: ${fraudType}`,
      cardFingerprint ? `Card fingerprint: ${cardFingerprint}` : null,
      customerEmail ? `Customer: ${customerEmail}` : null,
    ]
      .filter(Boolean)
      .join(' | ');

    await this.sendUrgent('Stripe Early Fraud Warning', `Refunded and blocklisted. ${details}`, [
      NtfyTag.SHIELD,
      NtfyTag.MONEY,
      NtfyTag.SKULL,
    ]);
  }

  /**
   * Initialize the ntfy service with the configured URL
   */
  private static initialize(): void {
    if (this.isConfigured) {
      return;
    }

    this.ntfyUrl = process.env.NTFY_URL || null;
    this.isConfigured = true;

    if (!this.ntfyUrl) {
      signale.warn('[NTFY] NTFY_URL not configured - notifications will be skipped');
    } else {
      signale.info(`[NTFY] Initialized with URL: ${this.ntfyUrl}`);
    }
  }
}
