import { SourceControlProviderKind } from "@t3tools/contracts";
import { limitRecoveryCommand } from "./UsageLimitRecoveryWorker.ts";
import * as McpProviderSessions from "@t3tools/provider-core/server/McpProviderSessions";
import * as SourceControlProviderRegistry from "../sourceControl/SourceControlProviderRegistry.ts";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, it } from "@effect/vitest";
import { vi } from "vite-plus/test";
import {
  type ApplicationStoredEvent,
  CheckpointId,
  CheckpointRef,
  CommandId,
  ContextTransferId,
  EventId,
  MessageId,
  NodeId,
  RuntimeRequestId,
  TurnItemId,
  type ModelSelection,
  type OrchestrationV2Run,
  type OrchestrationV2ThreadProjection,
  ProjectId,
  type PullRequestDetail,
  type PullRequestComment,
  PullRequestOperationError,
  ProviderDriverKind,
  ProviderInstanceId,
  ProviderSessionId,
  ProviderThreadId,
  ProviderTurnId,
  RunId,
  ScheduledTaskId,
  ThreadId,
} from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as DateTime from "effect/DateTime";
import * as Deferred from "effect/Deferred";
import * as Fiber from "effect/Fiber";
import * as Layer from "effect/Layer";
import * as Path from "effect/Path";
import * as Queue from "effect/Queue";
import * as Stream from "effect/Stream";
import * as TestClock from "effect/testing/TestClock";
import * as SqlClient from "effect/sql/SqlClient";

import * as CheckpointStore from "../checkpointing/CheckpointStore.ts";
import * as GitWorkflow from "../git/GitWorkflowService.ts";
import * as ServerConfig from "../config.ts";
import * as SqlitePersistence from "../persistence/Sqlite.ts";
import * as OrchestrationEventStore from "../persistence/OrchestrationEventStore.ts";
import * as ProjectEnrichmentService from "../project/ProjectEnrichmentService.ts";
import * as ProjectService from "../project/ProjectService.ts";
import * as ServerSettings from "../serverSettings.ts";
import * as McpSessionRegistryTestkit from "../mcp/McpSessionRegistry.testkit.ts";
import * as ProviderInstanceRegistry from "../provider/ProviderInstanceRegistry.ts";
import type { ProviderInstance } from "@t3tools/provider-core/server/driver";
import * as VcsDriverRegistry from "../vcs/VcsDriverRegistry.ts";
import * as VcsProcess from "../vcs/VcsProcess.ts";
import * as WorkspacePaths from "../workspace/WorkspacePaths.ts";
import * as LegacyV1ThreadImporter from "./legacy/LegacyV1ThreadImporter.ts";
import * as Orchestrator from "./Orchestrator.ts";
import { queuedRunsInDeliveryOrder } from "./QueuedRunOrder.ts";
import { ROLLBACK_FAILED_MESSAGE } from "./CheckpointRollbackService.ts";
import * as EffectWorker from "./EffectWorker.ts";
import * as EffectOutbox from "./EffectOutbox.ts";
import * as EventSink from "./EventSink.ts";
import * as ProviderRuntimeRecoveryService from "./ProviderRuntimeRecoveryService.ts";
import * as ProjectionMaintenance from "./ProjectionMaintenance.ts";
import * as ProjectionStore from "./ProjectionStore.ts";
import * as PullRequestWatchReactor from "./PullRequestWatchReactor.ts";
import { PULL_REQUEST_WATCH_WAKE_LIMIT } from "./pullRequestWatch.ts";
import * as PullRequestService from "../pullRequest/PullRequestService.ts";
import * as ProjectStore from "./ProjectStore.ts";
import * as ProviderSessionManager from "./ProviderSessionManager.ts";
import * as RuntimeLayer from "./runtimeLayer.ts";
import { shellStreamItemFromThreadShell } from "./ShellStream.ts";
import { CodexProviderCapabilitiesV2 } from "./Adapters/CodexAdapterV2.ts";
import * as ThreadManagementService from "./ThreadManagementService.ts";
import * as ManagedProjectFolders from "../project/ManagedProjectFolders.ts";
import * as ScheduledTaskService from "../scheduledTasks/ScheduledTaskService.ts";
import * as Scheduler from "../scheduling/Scheduler.ts";
import * as SecretRequests from "../secrets/SecretRequests.ts";
import * as PreviewManager from "../preview/Manager.ts";
import * as TerminalManager from "../terminal/Manager.ts";
import * as ResourceCleanupService from "./ResourceCleanupService.ts";
import * as ThreadLaunchService from "./ThreadLaunchService.ts";
import * as ThreadCommandExecutor from "./ThreadCommandExecutor.ts";
import type * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter";

const layerPlatformTest = Layer.merge(
  NodeServices.layer,
  Layer.mock(SourceControlProviderRegistry.SourceControlProviderRegistry)({
    resolveLink: () => Effect.die("unused title link"),
  }),
);

const layerServerConfig = ServerConfig.layerTest(process.cwd(), {
  prefix: "t3-orchestration-v2-runtime-layer-",
});

const modelSelection = {
  instanceId: ProviderInstanceId.make("codex"),
  model: "gpt-5.4",
} satisfies ModelSelection;
const alternateInstanceId = ProviderInstanceId.make("codex_alternate");

const layerVcsDriverRegistryTest = VcsDriverRegistry.layer.pipe(
  Layer.provide(VcsProcess.layer),
  Layer.provide(layerServerConfig),
  Layer.provide(layerPlatformTest),
);

const layerCheckpointStoreTest = CheckpointStore.layer.pipe(
  Layer.provide(layerVcsDriverRegistryTest),
);
const layerGitWorkflowTest = Layer.mock(GitWorkflow.GitWorkflowService)({
  pruneWorktrees: () => Effect.void,
  createWorktree: () => Effect.succeed({} as never),
});
const layerProjectServiceTest = Layer.mock(ProjectService.ProjectService)({
  getById: () => Effect.succeedNone,
});

const driver = ProviderDriverKind.make("codex");
const orchestrationAdapter = {
  instanceId: modelSelection.instanceId,
  driver,
  getCapabilities: () => Effect.succeed(CodexProviderCapabilitiesV2),
  planSelectionTransition: () => Effect.succeed({ type: "apply_on_next_turn" }),
  openSession: () => Effect.die("sessions are not used by lifecycle tests"),
} as ProviderAdapter.ProviderAdapterV2["Service"];
const providerInstance = {
  instanceId: modelSelection.instanceId,
  driverKind: driver,
  continuationIdentity: {
    driverKind: driver,
    continuationKey: "codex:test",
  },
  displayName: "Codex test",
  enabled: true,
  snapshot: { getSnapshot: Effect.succeed({}) } as unknown as ProviderInstance["snapshot"],
  orchestrationAdapter,
  textGeneration: {} as ProviderInstance["textGeneration"],
} satisfies ProviderInstance;
const alternateProviderInstance = {
  ...providerInstance,
  instanceId: alternateInstanceId,
  continuationIdentity: {
    driverKind: driver,
    continuationKey: "codex:test:alternate",
  },
  displayName: "Codex alternate test",
  orchestrationAdapter: {
    ...orchestrationAdapter,
    instanceId: alternateInstanceId,
  },
} satisfies ProviderInstance;

const layerTestProviderInstanceRegistry = Layer.succeed(
  ProviderInstanceRegistry.ProviderInstanceRegistry,
  {
    getInstance: (instanceId) =>
      Effect.succeed(
        [providerInstance, alternateProviderInstance].find(
          (instance) => instanceId === instance.instanceId,
        ),
      ),
    listInstances: Effect.succeed([providerInstance, alternateProviderInstance]),
    listUnavailable: Effect.succeed([]),
    streamChanges: Stream.empty,
    subscribeChanges: Effect.never,
  },
);

const watchedPullRequestDetail = (input: {
  readonly projectId: ProjectId;
  readonly number: number;
  readonly at: string;
}): PullRequestDetail => ({
  provider: SourceControlProviderKind.make("github"),
  capabilities: {
    diff: true,
    comment: true,
    actions: [],
    mergeMethods: [],
    search: false,
    checksAlwaysReport: true,
    review: { inlineComment: false, reply: false, resolve: false, verdicts: [] },
    reviewers: { request: false, listCandidates: false },
  },
  viewerPermissions: {
    actions: [],
    comment: true,
    resolve: true,
    verdicts: [],
    requestReviewers: false,
  },
  projectId: input.projectId,
  projectTitle: "Watch wake",
  workspaceRoot: "/workspace/watch",
  repository: "pingdotgg/t3code",
  number: input.number,
  title: "Watched pull request",
  body: "",
  url: `https://github.com/pingdotgg/t3code/pull/${input.number}`,
  author: { login: "agent-user", name: null, avatarUrl: null },
  state: "open",
  isDraft: false,
  mergeability: "mergeable",
  additions: 1,
  deletions: 0,
  changedFiles: 1,
  headBranch: "feature",
  headSha: "abc1234def",
  baseBranch: "main",
  createdAt: input.at,
  updatedAt: input.at,
  mergedAt: null,
  closedAt: null,
  reviewers: [],
  labels: [],
  checks: [{ name: "lint", status: "failure", description: null, url: null }],
  mergeCapabilities: { merge: true, squash: true, rebase: true },
  viewer: "agent-user",
});

const seedProject = (input: {
  readonly projectId: ProjectId;
  readonly title: string;
  readonly workspaceRoot: string;
  readonly defaultModelSelection: ModelSelection | null;
  readonly createdAt: string;
}) =>
  Effect.flatMap(ProjectStore.ProjectStoreV2, (projects) =>
    projects.apply({
      sequence: 0,
      eventId: EventId.make(`seed:${input.projectId}`),
      aggregateKind: "project",
      aggregateId: input.projectId,
      occurredAt: input.createdAt,
      commandId: null,
      causationEventId: null,
      correlationId: null,
      metadata: {},
      type: "project.created",
      payload: {
        projectId: input.projectId,
        title: input.title,
        workspaceRoot: input.workspaceRoot,
        defaultModelSelection: input.defaultModelSelection,
        scripts: [],
        createdAt: input.createdAt,
        updatedAt: input.createdAt,
      },
    }),
  );

const moveProject = (projectId: ProjectId, workspaceRoot: string, updatedAt: string) =>
  Effect.flatMap(ProjectStore.ProjectStoreV2, (projects) =>
    projects.apply({
      sequence: 0,
      eventId: EventId.make(`move:${projectId}:${workspaceRoot}`),
      aggregateKind: "project",
      aggregateId: projectId,
      occurredAt: updatedAt,
      commandId: null,
      causationEventId: null,
      correlationId: null,
      metadata: {},
      type: "project.meta-updated",
      payload: { projectId, workspaceRoot, updatedAt },
    }),
  );

const layerTest = Layer.mergeAll(
  RuntimeLayer.layer,
  RuntimeLayer.layerEventSink,
  ProjectStore.layer,
  ProjectionStore.layer,
  EffectOutbox.layer,
  ThreadCommandExecutor.layer,
).pipe(
  Layer.provide(McpSessionRegistryTestkit.layer),
  Layer.provide(McpProviderSessions.layer),
  Layer.provide(SqlitePersistence.layerMemory),
  Layer.provide(layerCheckpointStoreTest),
  Layer.provide(layerServerConfig),
  Layer.provide(ServerSettings.layerTest()),
  Layer.provide(layerTestProviderInstanceRegistry),
  Layer.provide(layerGitWorkflowTest),
  Layer.provide(layerProjectServiceTest),
  Layer.provide(layerPlatformTest),
);


it.layer(layerTest)("issue-25644 runtime evidence", (it) => {
  it.effect.each(["reordered", "legacy"] as const)("observes actual promotions: %s", (fixture) =>
    Effect.gen(function* () {
      console.log("PROOF", JSON.stringify({fixture, layer:"RuntimeLayer.layer + EventSinkV2 + in-memory SQLite", limitation:"stub provider; orchestration promotion, not provider execution"}));
      const orchestrator = yield* Orchestrator.OrchestratorV2;
      const sink = yield* EventSink.EventSinkV2;
      const threadId = ThreadId.make(`evidence-${fixture}`);
      const mid = (alias: string) => MessageId.make(`${threadId}:${alias}`);
      const cid = (alias: string) => CommandId.make(`${threadId}:${alias}`);
      const projection = () => orchestrator.getThreadProjection(threadId);
      const aliasOf = (run: OrchestrationV2Run) => run.userMessageId.slice(threadId.length + 1);
      const snapshot = (label: string) => Effect.gen(function* () {
        const p = yield* projection();
        console.log("SNAPSHOT", JSON.stringify({fixture,label,runs:p.runs.map(r=>({alias:aliasOf(r),id:r.id,ordinal:r.ordinal,position:r.queuePosition,status:r.status,marker:r.queuedAheadOfCompletions ?? "absent"}))}));
        return p;
      });
      yield* orchestrator.dispatch({type:"thread.create",createdBy:"user",creationSource:"web",commandId:cid("create"),threadId,projectId:ProjectId.make(`${threadId}:project`),title:fixture,modelSelection,runtimeMode:"full-access",interactionMode:"default",branch:null,worktreePath:process.cwd()});
      const user = (alias: string, active = false) => orchestrator.dispatch({type:"message.dispatch",createdBy:"user",creationSource:"web",commandId:cid(alias),threadId,messageId:mid(alias),text:alias,attachments:[],modelSelection,dispatchMode:{type:active ? "start_immediately" : "queue_after_active"}});
      yield* user("A",true);
      const blocker = (yield* projection()).runs[0]!;
      const completion = (generation: number) => Effect.gen(function* () {
        const active = (yield* projection()).runs.find(r=>r.id===blocker.id)!;
        const taskIds = [NodeId.make(`${threadId}:task-${generation}`)];
        yield* sink.write({events:[{id:EventId.make(`${threadId}:eligible-${generation}`),type:"run.updated",threadId,runId:active.id,occurredAt:yield* DateTime.now,payload:{...active,delegatedCompletion:{disposition:"open",nextGeneration:generation+1,delivery:{messageId:mid(`C${generation}`),generation,taskIds}}}}]});
        yield* orchestrator.dispatch({type:"message.dispatch",createdBy:"agent",creationSource:"server",commandId:cid(`C${generation}`),threadId,messageId:mid(`C${generation}`),text:`C${generation}`,attachments:[],modelSelection,dispatchMode:{type:"queue_after_active"},delegatedCompletion:{parentRunId:active.id,generation,taskIds}});
      });
      yield* completion(1);
      yield* completion(2);
      yield* user("U");
      if(fixture === "reordered") yield* user("V");
      const runFor = (p: OrchestrationV2ThreadProjection, alias: string) => p.runs.find(r=>r.userMessageId===mid(alias))!;
      const beforeReject = yield* snapshot("before forbidden C2 -> C1");
      const rejectSequence = yield* orchestrator.getThreadEventSequence(threadId);
      const rejected = yield* orchestrator.dispatch({type:"queued-run.reorder",commandId:cid("forbidden"),threadId,runId:runFor(beforeReject,"C2").id,beforeRunId:runFor(beforeReject,"C1").id}).pipe(Effect.flip);
      console.log("REJECT", JSON.stringify({fixture,cause:rejected.cause}));
      assert.equal(rejected.cause,"Automatic completion deliveries cannot be reordered.");
      assert.deepEqual((yield* projection()).runs,beforeReject.runs);
      assert.equal(yield* orchestrator.getThreadEventSequence(threadId),rejectSequence);
      console.log("ASSERT forbidden reorder: persisted runs and event sequence unchanged");
      if(fixture === "reordered") {
        yield* orchestrator.dispatch({type:"queued-run.reorder",commandId:cid("reorder-U-before-C1"),threadId,runId:runFor(beforeReject,"U").id,beforeRunId:runFor(beforeReject,"C1").id});
        yield* completion(3);
      } else {
        const p = yield* projection();
        yield* sink.write({events:yield* Effect.forEach(["U","C1","C2"], (alias,index)=>Effect.gen(function*(){
          const run=runFor(p,alias);
          const {queuedAheadOfCompletions: _marker,...legacy}=run;
          return {id:EventId.make(`${threadId}:legacy-${alias}`),type:"run.updated" as const,threadId,runId:run.id,occurredAt:yield* DateTime.now,payload:{...legacy,queuePosition:index+1}};
        }))});
      }
      const ready = yield* snapshot("persisted queue before terminal A");
      const expected = fixture === "reordered" ? ["U","C1","C2","C3","V"] : ["C1","C2","U"];
      if(fixture === "legacy") {
        assert.deepEqual(["U","C1","C2"].map(a=>runFor(ready,a).queuePosition),[1,2,3]);
        assert.isTrue(ready.runs.every(r=>r.queuedAheadOfCompletions===undefined));
      }
      const promoted = yield* Queue.unbounded<RunId>();
      const barrier = yield* Deferred.make<void>();
      const startSequence = yield* orchestrator.getThreadEventSequence(threadId);
      yield* sink.stream({threadId,afterSequence:0}).pipe(Stream.runForEach(stored=>Effect.gen(function*(){
        const event=stored.event;
        console.log("EVENT",JSON.stringify({fixture,sequence:stored.sequence,type:event.type,runId:event.runId,...(event.type === "run.updated" ? {status:event.payload.status,position:event.payload.queuePosition,marker:event.payload.queuedAheadOfCompletions ?? "absent"}: {})}));
        if(stored.sequence===startSequence) yield* Deferred.succeed(barrier,undefined);
        if(stored.sequence>startSequence && event.type==="run.updated" && event.payload.status==="starting") yield* Queue.offer(promoted,event.payload.id);
      })),Effect.forkScoped);
      yield* Deferred.await(barrier);
      const terminal = (run: OrchestrationV2Run) => Effect.gen(function*(){
        const now=yield* DateTime.now;
        yield* sink.write({events:[{id:EventId.make(`${threadId}:terminal-${run.id}`),type:"run.updated",threadId,runId:run.id,...(run.rootNodeId===null?{}:{nodeId:run.rootNodeId}),providerInstanceId:run.providerInstanceId,occurredAt:now,payload:{...run,status:"completed",completedAt:now}}]});
      });
      let current=runFor(ready,"A");
      const seen: string[]=[];
      for(const wanted of expected) {
        yield* terminal(current);
        const id=yield* Queue.take(promoted);
        const p=yield* snapshot(`promoted after ${aliasOf(current)}`);
        current=p.runs.find(r=>r.id===id)!;
        seen.push(aliasOf(current));
        console.log("PROMOTION",JSON.stringify({fixture,wanted,actual:aliasOf(current),id}));
        assert.equal(p.runs.filter(r=>r.status==="starting").length,1);
        assert.equal(current.status,"starting");
      }
      const terminalBarrier=yield* Deferred.make<void>();
      const lastSequence=yield* orchestrator.getThreadEventSequence(threadId);
      yield* sink.stream({threadId,afterSequence:lastSequence}).pipe(Stream.runForEach(stored=>stored.event.type==="run.updated" && stored.event.payload.id===current.id && stored.event.payload.status==="completed" ? Deferred.succeed(terminalBarrier,undefined):Effect.void),Effect.forkScoped);
      yield* terminal(current);
      yield* Deferred.await(terminalBarrier);
      const end=yield* snapshot("end");
      assert.equal(end.runs.filter(r=>r.status==="queued" || r.status==="starting").length,0);
      assert.equal(new Set(seen).size,expected.length);
      assert.deepEqual(seen,expected);
      if(fixture==="reordered") assert.equal(runFor(ready,"U").queuedAheadOfCompletions,true);
      console.log("PASS",JSON.stringify({fixture,actualPromotions:seen,exactlyOneStarting:true,noSkippedOrDuplicates:true,noQueuedAtEnd:true}));
    }).pipe(Effect.provide(Layer.fresh(layerTest))));
});
