import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { RedisQueueService } from './redis-queue.service';

type EnqueueJobInput = {
  queueName: string;
  jobType: string;
  payload: Prisma.InputJsonValue;
  maxAttempts?: number;
  availableAt?: Date;
};

@Injectable()
export class JobsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly redisQueueService: RedisQueueService,
  ) {}

  async enqueue(input: EnqueueJobInput) {
    const job = await this.prisma.job.create({
      data: {
        queueName: input.queueName,
        jobType: input.jobType,
        payloadJson: input.payload,
        maxAttempts: input.maxAttempts ?? 3,
        availableAt: input.availableAt ?? new Date(),
      },
    });

    await this.redisQueueService.enqueue(
      input.queueName,
      input.jobType,
      { dbJobId: job.id },
      {
        jobId: job.id,
        delay: Math.max((job.availableAt.getTime() - Date.now()), 0),
        attempts: input.maxAttempts ?? 3,
        removeOnComplete: 500,
        removeOnFail: 500,
      },
    );

    return job;
  }

  async markProcessing(id: string) {
    const claim = await this.prisma.job.updateMany({
      where: {
        id,
        status: { in: ['queued', 'processing'] },
      },
      data: {
        status: 'processing',
        attempts: { increment: 1 },
      },
    });

    if (claim.count === 0) {
      return null;
    }

    return this.prisma.job.findUnique({ where: { id } });
  }

  findById(id: string) {
    return this.prisma.job.findUnique({ where: { id } });
  }

  complete(id: string) {
    return this.prisma.job.update({
      where: { id },
      data: {
        status: 'processed',
        processedAt: new Date(),
        errorMessage: null,
      },
    });
  }

  async retry(id: string, errorMessage: string, delayMs: number) {
    const job = await this.prisma.job.update({
      where: { id },
      data: {
        status: 'queued',
        availableAt: new Date(Date.now() + delayMs),
        errorMessage: errorMessage.slice(0, 500),
      },
    });

    await this.redisQueueService.enqueue(
      job.queueName,
      job.jobType,
      { dbJobId: job.id },
      {
        jobId: `${job.id}:retry:${job.attempts}`,
        delay: Math.max(delayMs, 0),
        attempts: Math.max(job.maxAttempts - job.attempts, 1),
        removeOnComplete: 500,
        removeOnFail: 500,
      },
    );

    return job;
  }

  fail(id: string, errorMessage: string) {
    return this.prisma.job.update({
      where: { id },
      data: {
        status: 'failed',
        failedAt: new Date(),
        errorMessage: errorMessage.slice(0, 500),
      },
    });
  }

  findAll(limit = 50, queueName?: string, status?: string) {
    return this.prisma.job.findMany({
      where: {
        queueName: queueName?.trim() || undefined,
        status: status?.trim() || undefined,
      },
      orderBy: [{ createdAt: 'desc' }],
      take: Math.min(Math.max(limit, 1), 100),
    });
  }
}
