import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  PolicyNotFoundError,
  InvalidStateError,
  NoActivePolicyToReplaceError,
  TargetMismatchError,
  UnauthorizedError,
} from "../lib/errors.generated";
import { draftReplacementPolicy, activePolicy, inactivePolicy } from "../testing/fixtures";
import { run } from "./replaceAuditPolicy";
import { replaceAuditPolicy } from "./replaceAuditPolicy.generated";
import { InsufficientPermissionError } from "@tailor-platform/erp-kit/module";

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

describe("replaceAuditPolicy", () => {
  it("atomically deactivates existing ACTIVE policy and activates DRAFT replacement", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(draftReplacementPolicy); // replacement policy (executeTakeFirst)
    spies.select.mockReturnValueOnce([activePolicy]); // all ACTIVE policies for entity (execute)
    spies.update
      .mockReturnValueOnce({ ...activePolicy, status: "INACTIVE" }) // deactivated
      .mockReturnValueOnce({ ...draftReplacementPolicy, status: "ACTIVE" }); // activated

    const result = await run(db, { replacementPolicyId: draftReplacementPolicy.id }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.deactivatedPolicy).toBeDefined();
      expect(result.value.activatedPolicy).toBeDefined();
    }
  });

  it("existing policy is INACTIVE after replacement", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(draftReplacementPolicy);
    spies.select.mockReturnValueOnce([activePolicy]);
    spies.update
      .mockReturnValueOnce({ ...activePolicy, status: "INACTIVE" })
      .mockReturnValueOnce({ ...draftReplacementPolicy, status: "ACTIVE" });

    const result = await run(db, { replacementPolicyId: draftReplacementPolicy.id }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.deactivatedPolicy.status).toBe("INACTIVE");
    }
  });

  it("replacement policy is ACTIVE after replacement", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(draftReplacementPolicy);
    spies.select.mockReturnValueOnce([activePolicy]);
    spies.update
      .mockReturnValueOnce({ ...activePolicy, status: "INACTIVE" })
      .mockReturnValueOnce({ ...draftReplacementPolicy, status: "ACTIVE" });

    const result = await run(db, { replacementPolicyId: draftReplacementPolicy.id }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.activatedPolicy.status).toBe("ACTIVE");
    }
  });

  it("no gap in audit coverage during replacement", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(draftReplacementPolicy);
    spies.select.mockReturnValueOnce([activePolicy]);
    spies.update
      .mockReturnValueOnce({ ...activePolicy, status: "INACTIVE" })
      .mockReturnValueOnce({ ...draftReplacementPolicy, status: "ACTIVE" });

    const result = await run(db, { replacementPolicyId: draftReplacementPolicy.id }, ctx);

    expect(result.ok).toBe(true);
    // Both updates happen in the same transaction, ensuring no gap in audit coverage
    expect(spies.update).toHaveBeenCalledTimes(2);
    // Verify deactivation then activation order via set calls
    expect(spies.set).toHaveBeenNthCalledWith(1, expect.objectContaining({ status: "INACTIVE" }));
    expect(spies.set).toHaveBeenNthCalledWith(2, expect.objectContaining({ status: "ACTIVE" }));
  });

  it("rejects when replacement policy is not in DRAFT status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(activePolicy); // not DRAFT

    const result = await run(db, { replacementPolicyId: activePolicy.id }, ctx);

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

  it("rejects when replacement policy is ACTIVE", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(activePolicy);

    const result = await run(db, { replacementPolicyId: activePolicy.id }, ctx);

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

  it("rejects when replacement policy is INACTIVE", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(inactivePolicy);

    const result = await run(db, { replacementPolicyId: inactivePolicy.id }, ctx);

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

  it("rejects when no existing ACTIVE policy for the same entity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(draftReplacementPolicy);
    spies.select.mockReturnValueOnce([]); // no ACTIVE policies for this entity

    const result = await run(db, { replacementPolicyId: draftReplacementPolicy.id }, ctx);

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

  it("rejects when replacement targets different operationType than existing ACTIVE policy", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(draftReplacementPolicy);
    spies.select.mockReturnValueOnce([
      {
        ...activePolicy,
        operationType: "DELETE",
      },
    ]);

    const result = await run(db, { replacementPolicyId: draftReplacementPolicy.id }, ctx);

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

  it("rejects when replacement targets different companyId than existing ACTIVE policy", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(draftReplacementPolicy);
    spies.select.mockReturnValueOnce([
      {
        ...activePolicy,
        companyId: "different-company",
      },
    ]);

    const result = await run(db, { replacementPolicyId: draftReplacementPolicy.id }, ctx);

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

  it("selects correct ACTIVE policy when multiple ACTIVE policies exist for the same entity", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const deleteActive = { ...activePolicy, id: "policy-delete", operationType: "DELETE" };
    // Replacement targets UPDATE operation
    spies.select.mockReturnValueOnce(draftReplacementPolicy);
    // All ACTIVE policies for this entity returned by execute()
    // The implementation filters in-memory by companyId + operationType
    spies.select.mockReturnValueOnce([deleteActive, activePolicy]);
    spies.update
      .mockReturnValueOnce({ ...activePolicy, status: "INACTIVE" })
      .mockReturnValueOnce({ ...draftReplacementPolicy, status: "ACTIVE" });

    const result = await run(db, { replacementPolicyId: draftReplacementPolicy.id }, ctx);

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.deactivatedPolicy.status).toBe("INACTIVE");
      expect(result.value.activatedPolicy.status).toBe("ACTIVE");
    }
  });

  it("rejects when replacement policy does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined);

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

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

  it("rejects when caller lacks manageAuditPolicies permission", async () => {
    const { db } = createMockDb<Transaction>();
    const denied = { actorId: "test-actor", permissions: [] as string[] };
    const cmd = replaceAuditPolicy();

    const result = await cmd(db, { replacementPolicyId: draftReplacementPolicy.id }, denied);

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

  it("rejects when company-scoped caller replaces policy for a different company", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(draftReplacementPolicy); // policy with companyId: "company-1"
    const scopedCtx = { ...ctx, companyId: "company-2" };

    const result = await run(db, { replacementPolicyId: draftReplacementPolicy.id }, scopedCtx);

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

  it("rejects when global-scoped caller replaces company-scoped policy", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(draftReplacementPolicy); // policy with companyId: "company-1"
    const globalCtx = { ...ctx, companyId: null };

    const result = await run(db, { replacementPolicyId: draftReplacementPolicy.id }, globalCtx);

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