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 {
  ChannelNotFoundError,
  DuplicateTemplateKeyError,
  EventTypeNotFoundError,
  MissingRequiredFieldError,
  UndeclaredVariableReferenceError,
} from "../lib/errors.generated";
import {
  baseEmailChannel,
  baseEventCategoryBinding,
  baseInAppChannel,
  baseTemplate,
  baseEmailTemplate,
} from "../testing/fixtures";
import { run } from "./createNotificationTemplate";

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

  const validInput = {
    eventType: "PO_ISSUED",
    channelId: "channel-1",
    locale: "en-US",
    subject: "PO {{poNumber}} issued",
    body: "Purchase order {{poNumber}} has been issued.",
    variableSchema: '{"variables":[{"name":"poNumber","required":true,"type":"string"}]}',
  };

  it("creates a NotificationTemplate row for a new (eventType, channelId, locale) triple", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    // 1. event binding, 2. channel, 3. existing template (none), 4. insert template
    mock.enqueueResults([baseEventCategoryBinding], [baseInAppChannel], [], [baseTemplate]);

    const result = await mock.withTx((trx) => run(trx, validInput, ctx));

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.template.eventType).toBe("PO_ISSUED");
      expect(result.value.template.channelId).toBe("channel-1");
      expect(result.value.template.locale).toBe("en-US");
    }
    expect(mock.inserts.length).toBeGreaterThan(0);
  });

  it("creates an EMAIL template that includes htmlBody alongside subject and body", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([baseEventCategoryBinding], [baseEmailChannel], [], [baseEmailTemplate]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          ...validInput,
          channelId: "channel-2",
          htmlBody: "<p>Purchase order {{poNumber}} has been issued.</p>",
        },
        ctx,
      ),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.template.htmlBody).toBe(
        "<p>Purchase order {{poNumber}} has been issued.</p>",
      );
    }
  });

  it("creates an IN_APP template without htmlBody", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([baseEventCategoryBinding], [baseInAppChannel], [], [baseTemplate]);

    const result = await mock.withTx((trx) => run(trx, validInput, ctx));

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.template.htmlBody).toBeNull();
    }
  });

  it("returns DUPLICATE_TEMPLATE_KEY when a row for the same triple already exists", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([baseEventCategoryBinding], [baseInAppChannel], [baseTemplate]);

    const result = await mock.withTx((trx) => run(trx, validInput, ctx));

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

  it("returns CHANNEL_NOT_FOUND when channelId does not match any NotificationChannel row", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([baseEventCategoryBinding], []);

    const result = await mock.withTx((trx) => run(trx, validInput, ctx));

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

  it("returns EVENT_TYPE_NOT_FOUND when eventType is not in the Event Catalog", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) => run(trx, validInput, ctx));

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

  it("returns MISSING_REQUIRED_FIELD when subject is blank", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();

    const result = await mock.withTx((trx) => run(trx, { ...validInput, subject: "   " }, ctx));

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

  it("returns MISSING_REQUIRED_FIELD when variableSchema is omitted", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();

    const result = await mock.withTx((trx) => run(trx, { ...validInput, variableSchema: "" }, ctx));

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

  it("returns UNDECLARED_VARIABLE_REFERENCE when body references a variable not in variableSchema", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([baseEventCategoryBinding], [baseInAppChannel], []);

    const result = await mock.withTx((trx) =>
      run(trx, { ...validInput, body: "Hello {{undeclared}} from {{poNumber}}" }, ctx),
    );

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

  it("subsequent renders for the triple resolve to the newly created row", async () => {
    const mock = createKyselyMock<Namespace[keyof Namespace]>();
    mock.enqueueResults([baseEventCategoryBinding], [baseInAppChannel], [], [baseTemplate]);

    const result = await mock.withTx((trx) => run(trx, validInput, ctx));

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