import type { CommandContext } from "@tailor-platform/erp-kit/core";
import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import { ForbiddenError } from "../lib/errors.generated";
import { baseSubscription } from "../testing/fixtures";
import { run } from "./createNotificationSubscription";

describe("createNotificationSubscription", () => {
  const ctx: CommandContext = {
    actorId: "user-1",
    permissions: [],
  };

  it("inserts a NotificationSubscription row with subscribedAt set to current timestamp on first call", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    const now = new Date();
    // 1. check existing (none), 2. insert subscription
    mock.enqueueResults([], [{ ...baseSubscription, subscribedAt: now }]);

    const result = await mock.withTx((trx) =>
      run(trx, { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "po-1" }, ctx),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.subscription.subscribedAt).toBeDefined();
    }
    expect(mock.inserts.length).toBeGreaterThan(0);
  });

  it("returns the existing row without inserting a duplicate when called a second time with the same triple (idempotent)", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([{ ...baseSubscription }]);

    const result = await mock.withTx((trx) =>
      run(trx, { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "po-1" }, ctx),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.subscription.id).toBe(baseSubscription.id);
    }
    expect(mock.inserts.length).toBe(0);
  });

  it("subscribedAt is not refreshed on idempotent re-call", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([{ ...baseSubscription }]);

    const result = await mock.withTx((trx) =>
      run(trx, { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "po-1" }, ctx),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.subscription.subscribedAt).toEqual(baseSubscription.subscribedAt);
    }
    expect(mock.updates.length).toBe(0);
    expect(mock.inserts.length).toBe(0);
  });

  it("creates two distinct rows when two different users subscribe to the same source", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    // call 1: select (none), insert; call 2: select (none), insert
    mock.enqueueResults(
      [],
      [{ ...baseSubscription, id: "subscription-a", userId: "user-1" }],
      [],
      [{ ...baseSubscription, id: "subscription-b", userId: "user-2" }],
    );

    const r1 = await mock.withTx((trx) =>
      run(trx, { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "po-1" }, ctx),
    );
    const r2 = await mock.withTx((trx) =>
      run(
        trx,
        { userId: "user-2", sourceType: "PURCHASE_ORDER", sourceId: "po-1" },
        { ...ctx, actorId: "user-2" },
      ),
    );

    expect(r1.ok).toBe(true);
    expect(r2.ok).toBe(true);
    expect(mock.inserts.length).toBe(2);
  });

  it("creates two distinct rows when the same user subscribes to two different sourceIds under the same sourceType", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults(
      [],
      [{ ...baseSubscription, id: "sub-a", sourceId: "po-1" }],
      [],
      [{ ...baseSubscription, id: "sub-b", sourceId: "po-2" }],
    );

    const r1 = await mock.withTx((trx) =>
      run(trx, { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "po-1" }, ctx),
    );
    const r2 = await mock.withTx((trx) =>
      run(trx, { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "po-2" }, ctx),
    );

    expect(r1.ok).toBe(true);
    expect(r2.ok).toBe(true);
    expect(mock.inserts.length).toBe(2);
  });

  it("creates two distinct rows when the same user subscribes to the same sourceId under two different sourceTypes", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults(
      [],
      [{ ...baseSubscription, id: "sub-a", sourceType: "PURCHASE_ORDER" }],
      [],
      [{ ...baseSubscription, id: "sub-b", sourceType: "RFQ" }],
    );

    const r1 = await mock.withTx((trx) =>
      run(trx, { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "shared-id" }, ctx),
    );
    const r2 = await mock.withTx((trx) =>
      run(trx, { userId: "user-1", sourceType: "RFQ", sourceId: "shared-id" }, ctx),
    );

    expect(r1.ok).toBe(true);
    expect(r2.ok).toBe(true);
    expect(mock.inserts.length).toBe(2);
  });

  it("returns FORBIDDEN when the caller is not the target user", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "po-1" },
        { ...ctx, actorId: "user-other" },
      ),
    );

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(ForbiddenError);
    }
  });

  it("accepts a sourceId that does not correspond to any existing record (no source-existence validation)", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([], [{ ...baseSubscription, sourceId: "nonexistent-record" }]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "nonexistent-record" },
        ctx,
      ),
    );

    expect(result.ok).toBe(true);
    expect(mock.inserts.length).toBeGreaterThan(0);
  });

  it("concurrent re-subscribe calls produce exactly one row (uniqueness enforced at storage layer)", async () => {
    // Simulate one call that loses the race: the second call's SELECT finds the
    // row inserted by the first call and short-circuits without inserting.
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    // call 1: select (none), insert; call 2: select (found) -> no insert
    mock.enqueueResults([], [{ ...baseSubscription }], [{ ...baseSubscription }]);

    const r1 = await mock.withTx((trx) =>
      run(trx, { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "po-1" }, ctx),
    );
    const r2 = await mock.withTx((trx) =>
      run(trx, { userId: "user-1", sourceType: "PURCHASE_ORDER", sourceId: "po-1" }, ctx),
    );

    expect(r1.ok).toBe(true);
    expect(r2.ok).toBe(true);
    expect(mock.inserts.length).toBe(1);
  });
});
