import { Processor, WorkerHost, OnWorkerEvent } from "@nestjs/bullmq";
import { Injectable, Inject } from "@nestjs/common";
import { Logger } from "nestjs-pino";
import { Job } from "bullmq";
import { SendGridEmailProvider } from "../providers/sendgrid.provider.js";
import { CacheService } from "../../cache/cache.service.js";
import type { EmailJobData } from "./email.queue.js";
import { QUEUE_NAMES } from "../../queue/queue.constants.js";

interface ParsedSendGridError {
  statusCode: number;
  message: string;
  errors: { message: string; field?: string; help?: string }[];
  isRetryable: boolean;
}

/** TTL for idempotency keys (24 hours) - prevents duplicate sends on delayed retries */
const IDEMPOTENCY_TTL_SECONDS = 86400;
const IDEMPOTENCY_PREFIX = "email:sent:";

@Processor(QUEUE_NAMES.EMAIL)
@Injectable()
export class EmailProcessor extends WorkerHost {
  constructor(
    private readonly provider: SendGridEmailProvider,
    private readonly cache: CacheService,
    @Inject(Logger) private readonly logger: Logger
  ) {
    super();
  }

  async process(job: Job<EmailJobData>): Promise<void> {
    const jobContext = {
      service: "email-processor",
      jobId: job.id,
      attempt: job.attemptsMade + 1,
      maxAttempts: job.opts.attempts || 3,
      category: job.data.category || "transactional",
      subject: job.data.subject,
      recipientCount: Array.isArray(job.data.to) ? job.data.to.length : 1,
    };

    this.logger.log("Processing email job", jobContext);

    // Idempotency check: prevent duplicate sends on retries
    const idempotencyKey = `${IDEMPOTENCY_PREFIX}${job.id}`;
    const isFirstAttempt = await this.cache.setIfNotExists(
      idempotencyKey,
      { sentAt: Date.now(), subject: job.data.subject },
      IDEMPOTENCY_TTL_SECONDS
    );

    if (!isFirstAttempt) {
      this.logger.warn("Email already sent - skipping duplicate", {
        ...jobContext,
        idempotencyKey,
      });
      return; // Job completes successfully without sending again
    }

    try {
      // Send directly via provider (bypasses EmailService to avoid recursion)
      await this.provider.send(job.data);

      this.logger.log("Email job completed successfully", {
        ...jobContext,
        duration: Date.now() - job.timestamp,
      });
    } catch (error) {
      // Clear idempotency key on failure to allow retry
      await this.cache.del(idempotencyKey);

      const errorInfo = this.extractErrorInfo(error);

      this.logger.error("Email job failed", {
        ...jobContext,
        error: errorInfo.message,
        statusCode: errorInfo.statusCode,
        isRetryable: errorInfo.isRetryable,
        willRetry: errorInfo.isRetryable && jobContext.attempt < jobContext.maxAttempts,
      });

      // Re-throw to let BullMQ handle retry logic
      throw error;
    }
  }

  @OnWorkerEvent("completed")
  onCompleted(job: Job<EmailJobData>): void {
    this.logger.debug("Email job marked complete", {
      service: "email-processor",
      jobId: job.id,
      category: job.data.category,
    });
  }

  @OnWorkerEvent("failed")
  onFailed(job: Job<EmailJobData> | undefined, error: Error): void {
    if (!job) {
      this.logger.error("Email job failed (job undefined)", {
        service: "email-processor",
        error: error.message,
      });
      return;
    }

    const errorInfo = this.extractErrorInfo(error);
    const isLastAttempt = job.attemptsMade >= (job.opts.attempts || 3);

    if (isLastAttempt) {
      this.logger.error("Email job permanently failed - all retries exhausted", {
        service: "email-processor",
        jobId: job.id,
        category: job.data.category,
        subject: job.data.subject,
        totalAttempts: job.attemptsMade,
        error: errorInfo.message,
        statusCode: errorInfo.statusCode,
      });
    } else {
      this.logger.warn("Email job failed - will retry", {
        service: "email-processor",
        jobId: job.id,
        category: job.data.category,
        attempt: job.attemptsMade,
        maxAttempts: job.opts.attempts || 3,
        error: errorInfo.message,
        isRetryable: errorInfo.isRetryable,
      });
    }
  }

  @OnWorkerEvent("error")
  onError(error: Error): void {
    this.logger.error("Email processor worker error", {
      service: "email-processor",
      error: error.message,
      stack: error.stack,
    });
  }

  private extractErrorInfo(error: unknown): {
    message: string;
    statusCode?: number;
    isRetryable: boolean;
  } {
    // Check for SendGrid-enriched error
    if (
      error instanceof Error &&
      "sendgrid" in error &&
      typeof (error as Error & { sendgrid?: ParsedSendGridError }).sendgrid === "object"
    ) {
      const sgError = (error as Error & { sendgrid: ParsedSendGridError }).sendgrid;
      return {
        message: sgError.message,
        statusCode: sgError.statusCode,
        isRetryable: sgError.isRetryable,
      };
    }

    // Fallback for generic errors
    return {
      message: error instanceof Error ? error.message : String(error),
      isRetryable: false,
    };
  }
}
