import { InsufficientPermissionError } from "@tailor-platform/erp-kit/core";
import type { QueryContext } 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 { AuditEntryNotFoundError, UnauthorizedError } from "../lib/errors.generated";
import {
  baseAuditEntry,
  globalAuditEntry,
  baseChangeDetail,
  maskedChangeDetail,
  excludedChangeDetail,
  createChangeDetail,
} from "../testing/fixtures";
import { run } from "./getChangeDetails";

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

describe("getChangeDetails", () => {
  it("returns all change details for an audit entry", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry], [baseChangeDetail, maskedChangeDetail]);

    const result = await run(mock.db, { auditEntryId: "entry-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.changeDetails).toEqual([baseChangeDetail, maskedChangeDetail]);
      expect(result.value.changeDetails).toHaveLength(2);
    }
  });

  it("returns empty list when audit entry has no change details", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry], []);

    const result = await run(mock.db, { auditEntryId: "entry-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.changeDetails).toEqual([]);
    }
  });

  it("returns change details with masked values for MASK sensitivity mode", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry], [maskedChangeDetail]);

    const result = await run(mock.db, { auditEntryId: "entry-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      const detail = result.value.changeDetails[0];
      expect(detail.oldValue).toBe("***");
      expect(detail.newValue).toBe("***");
    }
  });

  it("returns change details with hashed values for HASH sensitivity mode", async () => {
    const hashedDetail = {
      id: "detail-hash",
      auditEntryId: "entry-1",
      fieldName: "status",
      oldValue: "sha256:abc123",
      newValue: "sha256:def456",
      createdAt: new Date("2024-01-15T10:30:00.000Z"),
      updatedAt: null,
    };
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry], [hashedDetail]);

    const result = await run(mock.db, { auditEntryId: "entry-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      const detail = result.value.changeDetails[0];
      expect(detail.oldValue).toBe("sha256:abc123");
      expect(detail.newValue).toBe("sha256:def456");
    }
  });

  it("returns change details with null values for EXCLUDE sensitivity mode", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry], [excludedChangeDetail]);

    const result = await run(mock.db, { auditEntryId: "entry-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      const detail = result.value.changeDetails[0];
      expect(detail.oldValue).toBeNull();
      expect(detail.newValue).toBeNull();
    }
  });

  it("returns change details with raw values for CAPTURE sensitivity mode", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry], [baseChangeDetail]);

    const result = await run(mock.db, { auditEntryId: "entry-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      const detail = result.value.changeDetails[0];
      expect(detail.oldValue).toBe("100.00");
      expect(detail.newValue).toBe("95.00");
    }
  });

  it("returns change details for company-scoped entry when caller has matching company permission", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry], [baseChangeDetail]);
    const companyScopedCtx: QueryContext = {
      actorId: "test-actor",
      permissions: ["audit:audit:viewAuditHistory"],
      companyId: "company-1",
    };

    const result = await run(mock.db, { auditEntryId: baseAuditEntry.id }, companyScopedCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.changeDetails).toEqual([baseChangeDetail]);
    }
  });

  it("returns change details for global entry when caller has global-scoped permission", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([globalAuditEntry], [createChangeDetail]);
    const globalScopedCtx: QueryContext = {
      actorId: "test-actor",
      permissions: ["audit:audit:viewAuditHistory"],
      companyId: null,
    };

    const result = await run(mock.db, { auditEntryId: globalAuditEntry.id }, globalScopedCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.changeDetails).toEqual([createChangeDetail]);
    }
  });

  it("returns error when audit entry does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await run(mock.db, { auditEntryId: "nonexistent" }, ctx);

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

  it("rejects when caller lacks permission for entry's company scope", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]); // companyId: "company-1"
    const scopedCtx: QueryContext = {
      actorId: "test-actor",
      permissions: ["audit:audit:viewAuditHistory"],
      companyId: "company-2",
    };

    const result = await run(mock.db, { auditEntryId: baseAuditEntry.id }, scopedCtx);

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

  it("rejects when caller lacks viewAuditHistory permission", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const noPermsCtx: QueryContext = { actorId: "test-actor", permissions: [] };

    const result = await run(mock.db, { auditEntryId: "entry-1" }, noPermsCtx);

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