import { promises as fs } from 'fs';
import * as os from 'os';
import * as path from 'path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';

import { readOpenCodeRuntimeLaneIndex } from '../../../../src/main/services/team/opencode/store/OpenCodeRuntimeManifestEvidenceReader';
import { TeamInboxWriter } from '../../../../src/main/services/team/TeamInboxWriter';
import { getTeamsBasePath, setClaudeBasePathOverride } from '../../../../src/main/utils/pathDecoder';

import {
  createOpenCodeLiveHarness,
  getRuntimeTranscript,
  type InboxMessage,
  waitForMemberInboxMessage,
  waitForOpenCodeLanesStopped,
  waitForOpenCodePeerRelay,
  waitForUserInboxReply,
} from './openCodeLiveTestHarness';

import type { TeamProvisioningProgress } from '../../../../src/shared/types';

const liveDescribe =
  process.env.OPENCODE_E2E === '1' && process.env.OPENCODE_E2E_SEMANTIC_MESSAGING === '1'
    ? describe
    : describe.skip;

const PROJECT_PATH = process.env.OPENCODE_E2E_PROJECT_PATH?.trim() || process.cwd();
const DEFAULT_MODEL = 'opencode/big-pickle';

liveDescribe('OpenCode semantic messaging live e2e', () => {
  let tempDir: string;
  let tempClaudeRoot: string;

  beforeEach(async () => {
    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'opencode-semantic-message-e2e-'));
    tempClaudeRoot = path.join(tempDir, '.claude');
    await fs.mkdir(tempClaudeRoot, { recursive: true });
    setClaudeBasePathOverride(tempClaudeRoot);
  });

  afterEach(async () => {
    setClaudeBasePathOverride(null);
    if (process.env.OPENCODE_E2E_KEEP_TEMP === '1') {
      console.info(`[OpenCodeSemanticMessaging.live] preserved temp dir: ${tempDir}`);
    } else {
      await fs.rm(tempDir, { recursive: true, force: true });
    }
  });

  it(
    'delivers a desktop message to an OpenCode member and records the reply through agent-teams_message_send',
    async () => {
      const { bridgeClient, selectedModel, svc, dispose } = await createOpenCodeLiveHarness({
        tempDir,
        selectedModel: process.env.OPENCODE_E2E_MODEL?.trim() || DEFAULT_MODEL,
        projectPath: PROJECT_PATH,
      });

      const teamName = `opencode-semantic-message-${Date.now()}`;
      const memberName = 'bob';
      const expectedReply = `opencode-semantic-message-e2e-${Date.now()}`;
      const progressEvents: TeamProvisioningProgress[] = [];

      try {
        const { runId } = await svc.createTeam(
          {
            teamName,
            cwd: PROJECT_PATH,
            providerId: 'opencode',
            model: selectedModel,
            skipPermissions: true,
            members: [
              {
                name: memberName,
                role: 'Developer',
                providerId: 'opencode',
                model: selectedModel,
              },
            ],
          },
          (progress) => {
            progressEvents.push(progress);
          }
        );

        expect(runId).toBeTruthy();
        const progressDump = progressEvents
          .map((progress) =>
            [
              progress.state,
              progress.message,
              progress.messageSeverity,
              progress.error,
              progress.cliLogsTail,
            ]
              .filter(Boolean)
              .join(' | ')
          )
          .join('\n');
        expect(
          progressEvents.some((progress) =>
            progress.message.includes('OpenCode team launch is ready')
          ),
          progressDump
        ).toBe(true);
        const runtimeSnapshot = await svc.getTeamAgentRuntimeSnapshot(teamName);
        expect(runtimeSnapshot.members[memberName]).toMatchObject({
          alive: true,
          runtimeModel: selectedModel,
        });
        await expect(readOpenCodeRuntimeLaneIndex(getTeamsBasePath(), teamName)).resolves.toMatchObject({
          lanes: {
            primary: {
              state: 'active',
            },
          },
        });

        const delivery = await svc.deliverOpenCodeMemberMessage(teamName, {
          memberName,
          messageId: `ui-message-${Date.now()}`,
          replyRecipient: 'user',
          text: [
            `Reply to the app Messages UI with exactly: ${expectedReply}`,
            'Use agent-teams_message_send with to="user" and from="bob".',
            'Do not answer only as plain assistant text.',
          ].join('\n'),
        });

        if (!delivery.delivered) {
          throw new Error(`OpenCode runtime delivery failed: ${JSON.stringify(delivery, null, 2)}`);
        }

        let reply: InboxMessage;
        try {
          reply = await waitForUserInboxReply(teamName, memberName, expectedReply, 90_000);
        } catch (error) {
          const transcript = await getRuntimeTranscript({
            bridgeClient,
            teamName,
            memberName,
            projectPath: PROJECT_PATH,
          });
          throw new Error(
            `${error instanceof Error ? error.message : String(error)}\nTranscript: ${JSON.stringify(
              transcript,
              null,
              2
            )}`
          );
        }
        expect(reply).toMatchObject({
          from: memberName,
          to: 'user',
        });
        expect(reply.text).toContain(expectedReply);
      } finally {
        await svc.stopTeam(teamName).catch(() => undefined);
        await dispose();
        await waitForOpenCodeLanesStopped(teamName);
      }
    },
    300_000
  );

  it(
    'relays a desktop inbox message to the OpenCode lead session and records the lead reply',
    async () => {
      const { bridgeClient, selectedModel, svc, dispose } = await createOpenCodeLiveHarness({
        tempDir,
        selectedModel: process.env.OPENCODE_E2E_MODEL?.trim() || DEFAULT_MODEL,
        projectPath: PROJECT_PATH,
      });

      const teamName = `opencode-lead-message-${Date.now()}`;
      const leadName = 'team-lead';
      const memberName = 'bob';
      const expectedReply = `opencode-lead-message-e2e-${Date.now()}`;
      const progressEvents: TeamProvisioningProgress[] = [];

      try {
        const { runId } = await svc.createTeam(
          {
            teamName,
            cwd: PROJECT_PATH,
            providerId: 'opencode',
            model: selectedModel,
            skipPermissions: true,
            members: [
              {
                name: memberName,
                role: 'Developer',
                providerId: 'opencode',
                model: selectedModel,
              },
            ],
          },
          (progress) => {
            progressEvents.push(progress);
          }
        );

        expect(runId).toBeTruthy();
        const progressDump = progressEvents
          .map((progress) =>
            [
              progress.state,
              progress.message,
              progress.messageSeverity,
              progress.error,
              progress.cliLogsTail,
            ]
              .filter(Boolean)
              .join(' | ')
          )
          .join('\n');
        expect(
          progressEvents.some((progress) =>
            progress.message.includes('OpenCode team launch is ready')
          ),
          progressDump
        ).toBe(true);

        const runtimeSnapshot = await svc.getTeamAgentRuntimeSnapshot(teamName);
        expect(runtimeSnapshot.members[leadName]).toMatchObject({
          alive: true,
          runtimeModel: selectedModel,
        });

        const written = await new TeamInboxWriter().sendMessage(teamName, {
          member: leadName,
          from: 'user',
          to: leadName,
          source: 'user_sent',
          text: [
            `Reply to the app Messages UI with exactly: ${expectedReply}`,
            `Use agent-teams_message_send with to="user" and from="${leadName}".`,
            'Do not answer only as plain assistant text.',
          ].join('\n'),
        });

        let lastRelay: Awaited<ReturnType<typeof svc.relayInboxFileToLiveRecipient>> | null = null;
        const deadline = Date.now() + 90_000;
        while (Date.now() < deadline) {
          lastRelay = await svc.relayInboxFileToLiveRecipient(teamName, leadName, {
            onlyMessageId: written.messageId,
            source: 'ui-send',
            deliveryMetadata: { replyRecipient: 'user' },
          });
          if (lastRelay.relayed >= 1) {
            break;
          }
          if (
            lastRelay.lastDelivery?.delivered === false &&
            lastRelay.lastDelivery.responsePending !== true
          ) {
            break;
          }
          await new Promise((resolve) => setTimeout(resolve, 3_000));
        }

        expect(lastRelay).toMatchObject({
          kind: 'opencode_member',
          relayed: 1,
        });

        let reply: InboxMessage;
        try {
          reply = await waitForUserInboxReply(teamName, leadName, expectedReply, 90_000);
        } catch (error) {
          const transcript = await getRuntimeTranscript({
            bridgeClient,
            teamName,
            memberName: leadName,
            projectPath: PROJECT_PATH,
          });
          throw new Error(
            `${error instanceof Error ? error.message : String(error)}\nLast relay: ${JSON.stringify(
              lastRelay,
              null,
              2
            )}\nTranscript: ${JSON.stringify(transcript, null, 2)}`
          );
        }
        expect(reply).toMatchObject({
          from: leadName,
          to: 'user',
        });
        expect(reply.text).toContain(expectedReply);
      } finally {
        await svc.stopTeam(teamName).catch(() => undefined);
        await dispose();
        await waitForOpenCodeLanesStopped(teamName);
      }
    },
    300_000
  );

  it(
    'relays an OpenCode teammate message into another OpenCode member runtime and records the reply',
    async () => {
      const { bridgeClient, selectedModel, svc, dispose } = await createOpenCodeLiveHarness({
        tempDir,
        selectedModel: process.env.OPENCODE_E2E_MODEL?.trim() || DEFAULT_MODEL,
        projectPath: PROJECT_PATH,
      });

      const teamName = `opencode-peer-message-${Date.now()}`;
      const senderName = 'bob';
      const recipientName = 'jack';
      const peerToken = `opencode-peer-inbox-e2e-${Date.now()}`;
      const replyToken = `opencode-peer-reply-e2e-${Date.now()}`;
      const peerInstructionText = [
        `Peer relay token: ${peerToken}.`,
        `Jack, reply to the app user with exactly ${replyToken}.`,
        `Use agent-teams_message_send to user from ${recipientName} with summary "peer reply".`,
      ].join(' ');
      const progressEvents: TeamProvisioningProgress[] = [];

      try {
        const { runId } = await svc.createTeam(
          {
            teamName,
            cwd: PROJECT_PATH,
            providerId: 'opencode',
            model: selectedModel,
            skipPermissions: true,
            members: [
              {
                name: senderName,
                role: 'Developer',
                providerId: 'opencode',
                model: selectedModel,
              },
              {
                name: recipientName,
                role: 'Developer',
                providerId: 'opencode',
                model: selectedModel,
              },
            ],
          },
          (progress) => {
            progressEvents.push(progress);
          }
        );

        expect(runId).toBeTruthy();
        const progressDump = progressEvents
          .map((progress) =>
            [
              progress.state,
              progress.message,
              progress.messageSeverity,
              progress.error,
              progress.cliLogsTail,
            ]
              .filter(Boolean)
              .join(' | ')
          )
          .join('\n');
        expect(
          progressEvents.some((progress) =>
            progress.message.includes('OpenCode team launch is ready')
          ),
          progressDump
        ).toBe(true);
        const runtimeSnapshot = await svc.getTeamAgentRuntimeSnapshot(teamName);
        expect(runtimeSnapshot.members[senderName]).toMatchObject({
          alive: true,
          runtimeModel: selectedModel,
        });
        expect(runtimeSnapshot.members[recipientName]).toMatchObject({
          alive: true,
          runtimeModel: selectedModel,
        });

        const senderDelivery = await svc.deliverOpenCodeMemberMessage(teamName, {
          memberName: senderName,
          messageId: `ui-peer-message-${Date.now()}`,
          replyRecipient: recipientName,
          text: [
            `Send one team message to ${recipientName}.`,
            'Use the exact message text below and no extra commentary:',
            peerInstructionText,
            `Call agent-teams_message_send with to="${recipientName}", from="${senderName}", text set to the exact message text above, and summary "peer relay".`,
            'Do not reply to user instead of sending the team message.',
          ].join('\n'),
        });

        if (!senderDelivery.delivered) {
          throw new Error(
            `OpenCode sender delivery failed: ${JSON.stringify(senderDelivery, null, 2)}`
          );
        }

        let peerMessage: InboxMessage & { messageId: string };
        try {
          peerMessage = await waitForMemberInboxMessage(
            teamName,
            recipientName,
            senderName,
            replyToken,
            180_000
          );
        } catch (error) {
          const transcript = await getRuntimeTranscript({
            bridgeClient,
            teamName,
            memberName: senderName,
            projectPath: PROJECT_PATH,
          });
          throw new Error(
            `${error instanceof Error ? error.message : String(error)}\n${senderName} transcript: ${JSON.stringify(
              transcript,
              null,
              2
            )}`
          );
        }

        await waitForOpenCodePeerRelay(
          svc,
          teamName,
          recipientName,
          peerMessage.messageId,
          180_000
        );

        let reply: InboxMessage;
        try {
          reply = await waitForUserInboxReply(teamName, recipientName, replyToken, 120_000);
        } catch (error) {
          const [senderTranscript, recipientTranscript] = await Promise.all([
            getRuntimeTranscript({
              bridgeClient,
              teamName,
              memberName: senderName,
              projectPath: PROJECT_PATH,
            }),
            getRuntimeTranscript({
              bridgeClient,
              teamName,
              memberName: recipientName,
              projectPath: PROJECT_PATH,
            }),
          ]);
          throw new Error(
            `${error instanceof Error ? error.message : String(error)}\n${senderName} transcript: ${JSON.stringify(
              senderTranscript,
              null,
              2
            )}\n${recipientName} transcript: ${JSON.stringify(recipientTranscript, null, 2)}`
          );
        }
        expect(reply).toMatchObject({
          from: recipientName,
          to: 'user',
        });
        expect(reply.text).toContain(replyToken);
      } finally {
        await svc.stopTeam(teamName).catch(() => undefined);
        await dispose();
        await waitForOpenCodeLanesStopped(teamName);
      }
    },
    360_000
  );
});
