import { describe, expect, it } from "vitest";
import {
  executeTrustedMerge,
  type TrustedMerger,
  type TrustedMergeRequest,
} from "@awp/application";
import {
  ProviderFailure,
  capability,
  unsafeOpaqueId,
  type ChangeSetId,
  type ConnectionId,
  type CorrelationId,
  type CredentialReferenceId,
  type OperationId,
  type PrincipalId,
  type ProjectId,
  type ProviderId,
  type TaskId,
  type AttemptId,
} from "@awp/contracts";
import { createCandidateManifest, type ChangeSet } from "@awp/domain";

const providerId = unsafeOpaqueId<ProviderId>("provider-github");
const changeSetId = unsafeOpaqueId<ChangeSetId>("changeset-1");
const manifest = createCandidateManifest({
  treeDigest: "tree-candidate",
  patchDigest: "patch-candidate",
  changedPaths: ["src/index.ts"],
  changes: [{ path: "src/index.ts", kind: "modify" }],
});
const publicationReference = {
  providerId,
  resourceType: "publication",
  nativeId: "publication-1",
  nativeRevision: "tree-candidate",
  observedAt: "2026-08-21T00:00:00Z",
};
const mergeReference = {
  providerId,
  resourceType: "merge",
  nativeId: "merge-1",
  nativeRevision: "merge-commit-123",
  observedAt: "2026-08-21T00:00:01Z",
};
const changeSet: ChangeSet = {
  id: changeSetId,
  projectId: unsafeOpaqueId<ProjectId>("project-1"),
  taskId: unsafeOpaqueId<TaskId>("task-1"),
  producerAttemptId: unsafeOpaqueId<AttemptId>("attempt-1"),
  baseIdentity: "base-commit",
  candidateDigest: manifest.treeDigest,
  candidateManifest: manifest,
  revision: 1,
  status: "ready-to-merge",
};
function request(): TrustedMergeRequest {
  return {
    context: {
      operationId: unsafeOpaqueId<OperationId>("operation-merge"),
      idempotencyKey: "merge:changeset-1",
      correlationId: unsafeOpaqueId<CorrelationId>("correlation-1"),
      authority: {
        principal: {
          id: unsafeOpaqueId<PrincipalId>("trusted-publisher"),
          kind: "system",
          capabilities: [capability("changeset.merge")],
        },
        capabilities: [capability("changeset.merge")],
      },
      connectionId: unsafeOpaqueId<ConnectionId>("connection-1"),
      credentialReferenceId: unsafeOpaqueId<CredentialReferenceId>("credential-1"),
    },
    changeSetId,
    repositoryKey: "repo-1",
    publicationReference,
    candidateDigest: manifest.treeDigest,
    candidateManifest: manifest,
    expectedBase: { reference: "refs/base/base-commit", revision: "base-commit" },
    expectedTarget: { reference: "refs/heads/main", revision: "target-before" },
  };
}

describe("trusted Merge mutation/reconciliation", () => {
  it("RT-007 reconciles exact successful result after ambiguous response before retry", async () => {
    let mergeCalls = 0;
    const merger: TrustedMerger = {
      async merge() {
        mergeCalls += 1;
        throw new ProviderFailure({
          category: "unavailable",
          retryable: true,
          providerId,
          resourceRef: publicationReference,
          safeMessage: "response lost after provider mutation",
          observedAt: "2026-08-21T00:00:01Z",
        });
      },
      async reconcile(input) {
        expect(input).toEqual(request());
        return {
          value: {
            state: "merged",
            result: { changeSetId, resultingRevision: "merge-commit-123", mergeReference },
          },
          references: [mergeReference],
          observedAt: "2026-08-21T00:00:02Z",
        };
      },
    };
    const result = await executeTrustedMerge(
      merger,
      changeSet,
      request(),
      request().expectedTarget,
    );
    expect(result.value.resultingRevision).toBe("merge-commit-123");
    expect(result.value.mergeReference.nativeRevision).toBe("merge-commit-123");
    expect(mergeCalls).toBe(1);
  });

  it("prevents candidate or exact target mismatch before provider mutation", async () => {
    let mergeCalls = 0;
    const merger: TrustedMerger = {
      async merge() {
        mergeCalls += 1;
        return {
          value: { changeSetId, resultingRevision: "unexpected", mergeReference },
          references: [mergeReference],
          observedAt: "2026-08-21T00:00:00Z",
        };
      },
      async reconcile() {
        return {
          value: { state: "not-merged" },
          references: [],
          observedAt: "2026-08-21T00:00:00Z",
        };
      },
    };
    const mismatchedCandidate = { ...request(), candidateDigest: "different-tree" };
    await expect(
      executeTrustedMerge(merger, changeSet, mismatchedCandidate, request().expectedTarget),
    ).rejects.toThrow(/candidate|digest/i);
    await expect(
      executeTrustedMerge(merger, changeSet, request(), {
        reference: "refs/heads/main",
        revision: "unexpected-target",
      }),
    ).rejects.toThrow(/target/i);
    expect(mergeCalls).toBe(0);
  });
});
