import { eq, sql, and, or, isNull, lte } from "drizzle-orm";
import type { MySql2Database } from "drizzle-orm/mysql2";
import { getDb } from "../../config/database.js";
import {
  outbox,
  type OutboxMessage,
  type NewOutboxMessage,
} from "./outbox.schema.js";

type DbClient = MySql2Database;

export class OutboxRepository {
  async create(message: NewOutboxMessage, tx?: DbClient): Promise<void> {
    const client = tx ?? getDb();
    await client.insert(outbox).values(message);
  }

  async findPending(limit: number = 100): Promise<OutboxMessage[]> {
    const now = new Date();
    return getDb()
      .select()
      .from(outbox)
      .where(
        and(
          eq(outbox.status, "pending"),
          or(isNull(outbox.nextRetryAt), lte(outbox.nextRetryAt, now)),
        ),
      )
      .limit(limit);
  }

  async markPublished(id: string): Promise<void> {
    await getDb()
      .update(outbox)
      .set({ status: "published", publishedAt: new Date(), lastError: null })
      .where(eq(outbox.id, id));
  }

  async incrementRetry(id: string, errorMessage: string): Promise<void> {
    const backoffMs = 5000 * 2 ** 1;
    await getDb()
      .update(outbox)
      .set({
        retryCount: sql`${outbox.retryCount} + 1`,
        nextRetryAt: new Date(Date.now() + backoffMs),
        lastError: errorMessage,
      })
      .where(eq(outbox.id, id));
  }

  async markFailed(id: string, errorMessage: string): Promise<void> {
    await getDb()
      .update(outbox)
      .set({ status: "failed", lastError: errorMessage })
      .where(eq(outbox.id, id));
  }
}

export const outboxRepository = new OutboxRepository();
