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 {
  assertManagedOverlayDoesNotShadowUserConfig,
  buildManagedOverlayConfig,
  OpenCodeBehaviorSourceScanner,
  OpenCodeManagedOverlayBuilder,
  pickAppOwnedMcpServerName,
} from '../../../../src/main/services/team/opencode/config/OpenCodeManagedOverlay';

describe('OpenCodeManagedOverlay', () => {
  let tempDir: string;
  let homePath: string;
  let projectPath: string;

  beforeEach(async () => {
    tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'opencode-managed-overlay-'));
    homePath = path.join(tempDir, 'home');
    projectPath = path.join(tempDir, 'project');
    await fs.mkdir(projectPath, { recursive: true });
  });

  afterEach(async () => {
    await fs.rm(tempDir, { recursive: true, force: true });
  });

  it('builds a minimal inline MCP overlay without user behavior keys or pure mode', async () => {
    await writeJson(path.join(projectPath, 'opencode.json'), {
      plugin: ['user-plugin'],
      mcp: { user_server: { type: 'local', command: 'custom', enabled: true } },
    });
    const builder = new OpenCodeManagedOverlayBuilder(
      new OpenCodeBehaviorSourceScanner({ homePath }),
      () => new Date('2026-04-21T12:00:00.000Z')
    );

    const overlay = await builder.build({
      projectPath,
      preferredMcpName: 'agent-teams',
      appMcpCommand: 'node',
      appMcpArgs: ['server.js'],
      appMcpEnv: { TEAM_RUNTIME: '1' },
    });
    const config = JSON.parse(overlay.env.OPENCODE_CONFIG_CONTENT);

    expect(config).toEqual({
      mcp: {
        'agent-teams': {
          type: 'local',
          command: 'node',
          args: ['server.js'],
          enabled: true,
          environment: { TEAM_RUNTIME: '1' },
          timeout: 10_000,
        },
      },
    });
    expect(overlay.env).not.toHaveProperty('OPENCODE_PURE');
    expect(overlay.env).not.toHaveProperty('OPENCODE_DISABLE_PROJECT_CONFIG');
    expect(overlay.env.OPENCODE_DISABLE_AUTOUPDATE).toBe('1');
    expect(config).not.toHaveProperty('plugin');
    expect(config).not.toHaveProperty('model');
    expect(overlay.diagnostics).toContain(
      'OpenCode managed overlay checked at 2026-04-21T12:00:00.000Z'
    );
  });

  it('allows app-managed OpenCode auto-update only behind an explicit override', async () => {
    const builder = new OpenCodeManagedOverlayBuilder(
      new OpenCodeBehaviorSourceScanner({ homePath })
    );

    const overlay = await builder.build({
      projectPath,
      preferredMcpName: 'agent-teams',
      appMcpCommand: 'node',
      appMcpArgs: ['server.js'],
      appMcpEnv: {},
      env: {
        CLAUDE_TEAM_OPENCODE_ALLOW_AUTOUPDATE: '1',
        OPENCODE_DISABLE_AUTOUPDATE: '1',
      },
    });

    expect(overlay.env.OPENCODE_CONFIG_CONTENT).toBeTruthy();
    expect(overlay.env.OPENCODE_DISABLE_AUTOUPDATE).toBeUndefined();
  });

  it('renames the app-owned MCP server when user config already declares the preferred name', async () => {
    await writeJson(path.join(projectPath, 'opencode.json'), {
      mcp: {
        'agent-teams': { type: 'local', command: 'custom', enabled: true },
        'agent-teams-runtime-1': { type: 'local', command: 'custom-2', enabled: true },
      },
    });
    const builder = new OpenCodeManagedOverlayBuilder(
      new OpenCodeBehaviorSourceScanner({ homePath })
    );

    const overlay = await builder.build({
      projectPath,
      preferredMcpName: 'agent-teams',
      appMcpCommand: 'node',
      appMcpArgs: ['server.js'],
      appMcpEnv: {},
    });

    expect(overlay.appMcpServerName).toBe('agent-teams-runtime-2');
    expect(overlay.diagnostics.join('\n')).toContain('already declares MCP server "agent-teams"');
  });

  it('reads JSONC project config and fingerprints plugin behavior sources', async () => {
    await fs.mkdir(path.join(projectPath, '.opencode/plugins'), { recursive: true });
    await fs.writeFile(
      path.join(projectPath, 'opencode.jsonc'),
      `{
        // user-owned MCP must be observed, not overwritten
        "mcp": {
          "agent-teams": { "type": "local", "command": "custom", "enabled": true }
        }
      }`,
      'utf8'
    );
    await fs.writeFile(
      path.join(projectPath, '.opencode/plugins/example.ts'),
      'export default {}',
      'utf8'
    );
    const scanner = new OpenCodeBehaviorSourceScanner({ homePath });

    await expect(scanner.readDeclaredMcpNames(projectPath)).resolves.toEqual(
      new Set(['agent-teams'])
    );
    const sources = await scanner.scan(projectPath);

    expect(sources).toContainEqual(
      expect.objectContaining({
        kind: 'project_plugin_dir',
        exists: true,
        fileCount: 1,
        fingerprint: expect.stringMatching(/^[a-f0-9]{64}$/),
      })
    );
    expect(sources).toContainEqual(
      expect.objectContaining({
        kind: 'project_opencode_dir',
        exists: true,
        fileCount: 1,
      })
    );
  });

  it('reads JSONC global config as a user-owned behavior source', async () => {
    await fs.mkdir(path.join(homePath, '.config/opencode'), { recursive: true });
    await fs.writeFile(
      path.join(homePath, '.config/opencode/opencode.jsonc'),
      `{
        // global user-owned MCP must be observed, not overwritten
        "mcp": {
          "agent-teams": { "type": "local", "command": "custom", "enabled": true }
        }
      }`,
      'utf8'
    );
    const scanner = new OpenCodeBehaviorSourceScanner({ homePath });

    await expect(scanner.readDeclaredMcpNames(projectPath)).resolves.toEqual(
      new Set(['agent-teams'])
    );
    const sources = await scanner.scan(projectPath);

    expect(sources).toContainEqual(
      expect.objectContaining({
        kind: 'global_config',
        exists: true,
        fileCount: 1,
        fingerprint: expect.stringMatching(/^[a-f0-9]{64}$/),
      })
    );
  });

  it('rejects managed overlays that would shadow user behavior keys', () => {
    expect(() =>
      assertManagedOverlayDoesNotShadowUserConfig({
        ...buildManagedOverlayConfig({
          serverName: 'agent-teams',
          command: 'node',
          args: [],
          environment: {},
          timeout: 10_000,
        }),
        plugin: ['managed-plugin'],
        model: 'managed-model',
      })
    ).toThrow('Managed OpenCode overlay must not set user behavior keys: plugin, model');
  });

  it('picks deterministic collision-safe app MCP names', () => {
    expect(pickAppOwnedMcpServerName('agent-teams', new Set())).toBe('agent-teams');
    expect(pickAppOwnedMcpServerName('agent-teams', new Set(['agent-teams']))).toBe(
      'agent-teams-runtime-1'
    );
    expect(
      pickAppOwnedMcpServerName(
        'agent-teams',
        new Set(['agent-teams', 'agent-teams-runtime-1', 'agent-teams-runtime-2'])
      )
    ).toBe('agent-teams-runtime-3');
  });
});

async function writeJson(filePath: string, value: unknown): Promise<void> {
  await fs.mkdir(path.dirname(filePath), { recursive: true });
  await fs.writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
}
