import type { QueryContext } from "@tailor-platform/erp-kit/core";
import { 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 {
  ValidationErrorError,
  InvalidScopeError,
  UnauthorizedError,
} from "../lib/errors.generated";
import { baseAuditEntry, globalAuditEntry } from "../testing/fixtures";
import { run } from "./searchAuditEntries";

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

describe("searchAuditEntries", () => {
  it("returns entries filtered by actorId", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
      expect(result.value.items[0]).toEqual(baseAuditEntry);
    }
  });

  it("returns entries filtered by actorType and actorId", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);

    const result = await run(mock.db, { actorType: "USER", actorId: "user-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
    }
  });

  it("returns entries filtered by entityType", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
    }
  });

  it("returns entries filtered by entityType and entityId", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);

    const result = await run(mock.db, { entityType: "SalesOrder", entityId: "so-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
    }
  });

  it("returns entries filtered by operationType", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
    }
  });

  it("returns entries filtered by date range", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);

    const result = await run(
      mock.db,
      {
        dateFrom: new Date("2024-01-01"),
        dateTo: new Date("2024-12-31"),
      },
      ctx,
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
    }
  });

  it("returns entries filtered by companyId", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
      expect(result.value.items[0].companyId).toBe("company-1");
    }
  });

  it("returns global entries when companyId is omitted", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([globalAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
      expect(result.value.items[0].companyId).toBeNull();
    }
  });

  it("returns only company entries when scope is COMPANY", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);

    const result = await run(mock.db, { scope: "COMPANY", companyId: "company-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
      expect(result.value.items[0].companyId).toBe("company-1");
    }
  });

  it("returns only global entries when scope is GLOBAL", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([globalAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
      expect(result.value.items[0].companyId).toBeNull();
    }
  });

  it("returns combined company and global entries when scope is ALL", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry, globalAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(2);
    }
  });

  it("restricts combined results to scopes the caller is authorized for when scope is ALL", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry, globalAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(2);
    }
  });

  it("returns only company entries in combined scope when caller lacks global permission", async () => {
    // Permission enforcement is middleware; query layer returns what DB gives
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
    }
  });

  it("returns only global entries in combined scope when caller lacks company permission", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([globalAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
    }
  });

  it("filters combined scope results by companyId when both scope=ALL and companyId are provided", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    // scope=ALL + companyId returns company entries + global entries (combined)
    mock.enqueueResults([baseAuditEntry, globalAuditEntry]);

    const result = await run(mock.db, { scope: "ALL", companyId: "company-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(2);
    }
  });

  it("rejects scope=COMPANY when companyId is not provided", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

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

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

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

    const result = await run(mock.db, { scope: "INVALID", entityType: "SalesOrder" }, ctx);

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

  it("returns entries filtered by correlationId", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([globalAuditEntry]);

    const result = await run(mock.db, { correlationId: "corr-uuid-1" }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
      expect(result.value.items[0].correlationId).toBe("corr-uuid-1");
    }
  });

  it("combines multiple filter criteria", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);

    const result = await run(
      mock.db,
      {
        actorId: "user-1",
        entityType: "SalesOrder",
        operationType: "UPDATE",
        companyId: "company-1",
      },
      ctx,
    );

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
    }
  });

  it("returns results in chronological order", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry, globalAuditEntry]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(2);
      // Default order is timestamp asc
      expect(result.value.items[0].timestamp.getTime()).toBeLessThanOrEqual(
        result.value.items[1].timestamp.getTime(),
      );
    }
  });

  it("paginates large result sets", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const manyEntries = Array.from({ length: 3 }, (_, i) => ({
      ...baseAuditEntry,
      id: `entry-${i}`,
    }));
    mock.enqueueResults(manyEntries);

    const result = await run(mock.db, { entityType: "SalesOrder", limit: 2 }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(2);
      expect(result.value.hasNextPage).toBe(true);
    }
  });

  it("returns empty list when no entries match", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

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

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(0);
      expect(result.value.hasNextPage).toBe(false);
    }
  });

  it("rejects when no filter parameters are provided", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const result = await run(mock.db, {}, ctx);

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

  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, { entityType: "SalesOrder" }, noPermsCtx);

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

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

    const result = await run(mock.db, { companyId: "company-2" }, scopedCtx);

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

  it("restricts company-scoped caller to their company even when scope=GLOBAL", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);
    const scopedCtx: QueryContext = {
      actorId: "test-actor",
      permissions: ["audit:audit:viewAuditHistory"],
      companyId: "company-1",
    };

    const result = await run(mock.db, { scope: "GLOBAL" }, scopedCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(0);
    }
  });

  it("restricts company-scoped caller to their company even when scope=ALL", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([baseAuditEntry]);
    const scopedCtx: QueryContext = {
      actorId: "test-actor",
      permissions: ["audit:audit:viewAuditHistory"],
      companyId: "company-1",
    };

    const result = await run(mock.db, { scope: "ALL" }, scopedCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
      expect(result.value.items[0].companyId).toBe("company-1");
    }
  });

  it("restricts global-scoped caller to global entries even when scope=COMPANY", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    const globalCtx: QueryContext = {
      actorId: "test-actor",
      permissions: ["audit:audit:viewAuditHistory"],
      companyId: null,
    };

    const result = await run(mock.db, { scope: "COMPANY", companyId: "company-1" }, globalCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(0);
    }
  });

  it("restricts global-scoped caller to global entries even when scope=ALL", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([globalAuditEntry]);
    const globalCtx: QueryContext = {
      actorId: "test-actor",
      permissions: ["audit:audit:viewAuditHistory"],
      companyId: null,
    };

    const result = await run(mock.db, { scope: "ALL" }, globalCtx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toHaveLength(1);
      expect(result.value.items[0].companyId).toBeNull();
    }
  });
});
