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 {
  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";

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

describe("replaceAuditPolicy", () => {
  it("atomically deactivates existing ACTIVE policy and activates DRAFT replacement", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [draftReplacementPolicy], // replacement policy
      [activePolicy], // all ACTIVE policies for entity
      [{ ...activePolicy, status: "INACTIVE" }], // deactivated
      [{ ...draftReplacementPolicy, status: "ACTIVE" }], // activated
    );

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [draftReplacementPolicy],
      [activePolicy],
      [{ ...activePolicy, status: "INACTIVE" }],
      [{ ...draftReplacementPolicy, status: "ACTIVE" }],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [draftReplacementPolicy],
      [activePolicy],
      [{ ...activePolicy, status: "INACTIVE" }],
      [{ ...draftReplacementPolicy, status: "ACTIVE" }],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [draftReplacementPolicy],
      [activePolicy],
      [{ ...activePolicy, status: "INACTIVE" }],
      [{ ...draftReplacementPolicy, status: "ACTIVE" }],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { replacementPolicyId: draftReplacementPolicy.id }, ctx),
    );

    expect(result.ok).toBe(true);
    // Both updates happen in the same transaction, ensuring no gap in audit coverage
    expect(mock.updates).toHaveLength(2);
    // Verify deactivation then activation order via set values
    expect(mock.updates[0].updateValues()).toEqual(expect.objectContaining({ status: "INACTIVE" }));
    expect(mock.updates[1].updateValues()).toEqual(expect.objectContaining({ status: "ACTIVE" }));
  });

  it("rejects when replacement policy is not in DRAFT status", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activePolicy]); // not DRAFT

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([activePolicy]);

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([inactivePolicy]);

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([draftReplacementPolicy], []); // no ACTIVE policies for this entity

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [draftReplacementPolicy],
      [
        {
          ...activePolicy,
          operationType: "DELETE",
        },
      ],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [draftReplacementPolicy],
      [
        {
          ...activePolicy,
          companyId: "different-company",
        },
      ],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    const deleteActive = { ...activePolicy, id: "policy-delete", operationType: "DELETE" };
    mock.enqueueResults(
      // Replacement targets UPDATE operation
      [draftReplacementPolicy],
      // All ACTIVE policies for this entity; implementation filters in-memory by companyId + operationType
      [deleteActive, activePolicy],
      [{ ...activePolicy, status: "INACTIVE" }],
      [{ ...draftReplacementPolicy, status: "ACTIVE" }],
    );

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    const denied = { actorId: "test-actor", permissions: [] as string[] };
    const cmd = replaceAuditPolicy();

    const result = await mock.withTx((trx) =>
      cmd(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([draftReplacementPolicy]); // policy with companyId: "company-1"
    const scopedCtx = { ...ctx, companyId: "company-2" };

    const result = await mock.withTx((trx) =>
      run(trx, { 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 mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([draftReplacementPolicy]); // policy with companyId: "company-1"
    const globalCtx = { ...ctx, companyId: null };

    const result = await mock.withTx((trx) =>
      run(trx, { replacementPolicyId: draftReplacementPolicy.id }, globalCtx),
    );

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