import { type CommandContext, InsufficientPermissionError } 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 {
  InvalidEntityNameError,
  DuplicateEntityNameError,
  InvalidEntityScopeError,
  InvalidAuditableFieldsError,
} from "../lib/errors.generated";
import { companyBoundEntity, globalEntity } from "../testing/fixtures";
import { run } from "./registerAuditableEntity";
import { registerAuditableEntity } from "./registerAuditableEntity.generated";

const ctx: CommandContext = {
  actorId: "test-actor",
  permissions: ["audit:audit:registerAuditableEntity"],
};

describe("registerAuditableEntity", () => {
  it("registers entity with COMPANY_BOUND scope", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([], [companyBoundEntity]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "SalesOrder",
          entityScope: "COMPANY_BOUND",
          auditableFields: ["amount", "status", "customerId"],
        },
        ctx,
      ),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.auditableEntity.entityName).toBe("SalesOrder");
      expect(result.value.auditableEntity.entityScope).toBe("COMPANY_BOUND");
    }
  });

  it("registers entity with GLOBAL scope", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([], [globalEntity]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "User",
          entityScope: "GLOBAL",
          auditableFields: ["email", "role", "isActive"],
        },
        ctx,
      ),
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.auditableEntity.entityName).toBe("User");
      expect(result.value.auditableEntity.entityScope).toBe("GLOBAL");
    }
  });

  it("rejects registration with empty entityName", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "",
          entityScope: "COMPANY_BOUND",
          auditableFields: ["amount"],
        },
        ctx,
      ),
    );

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

  it("rejects registration with duplicate entityName", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([companyBoundEntity]); // existing entity found

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "SalesOrder",
          entityScope: "COMPANY_BOUND",
          auditableFields: ["amount"],
        },
        ctx,
      ),
    );

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

  it("rejects registration with invalid entityScope", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "SalesOrder",
          entityScope: "INVALID_SCOPE",
          auditableFields: ["amount"],
        },
        ctx,
      ),
    );

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

  it("rejects registration with empty auditableFields", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "SalesOrder",
          entityScope: "COMPANY_BOUND",
          auditableFields: [],
        },
        ctx,
      ),
    );

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

  it("rejects registration with binary field type in auditableFields", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "SalesOrder",
          entityScope: "COMPANY_BOUND",
          auditableFields: [
            { fieldName: "amount", fieldType: "scalar" },
            { fieldName: "blobData", fieldType: "binary" },
          ],
        },
        ctx,
      ),
    );

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

  it("rejects registration with computed field type in auditableFields", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "SalesOrder",
          entityScope: "COMPANY_BOUND",
          auditableFields: [
            { fieldName: "amount", fieldType: "scalar" },
            { fieldName: "total", fieldType: "computed" },
          ],
        },
        ctx,
      ),
    );

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

  it("accepts mixed string and object field definitions in auditableFields", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([], [companyBoundEntity]);

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "SalesOrder",
          entityScope: "COMPANY_BOUND",
          auditableFields: ["amount", { fieldName: "status", fieldType: "scalar" }],
        },
        ctx,
      ),
    );

    expect(result.ok).toBe(true);
  });

  it("rejects registration with empty field name in auditableFields", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "SalesOrder",
          entityScope: "COMPANY_BOUND",
          auditableFields: ["amount", "", "status"],
        },
        ctx,
      ),
    );

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

  it("rejects registration with unknown field type in auditableFields", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await mock.withTx((trx) =>
      run(
        trx,
        {
          entityName: "SalesOrder",
          entityScope: "COMPANY_BOUND",
          auditableFields: [
            { fieldName: "amount", fieldType: "scalar" },
            { fieldName: "custom", fieldType: "nonsense" as never },
          ],
        },
        ctx,
      ),
    );

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

  it("rejects caller without registerAuditableEntity permission", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const command = registerAuditableEntity();
    const noPermCtx: CommandContext = {
      actorId: "test-actor",
      permissions: [],
    };

    const result = await mock.withTx((trx) =>
      command(
        trx,
        {
          entityName: "SalesOrder",
          entityScope: "COMPANY_BOUND",
          auditableFields: ["amount"],
        },
        noPermCtx,
      ),
    );

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