import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join, resolve } from 'node:path';
import { homedir } from 'node:os';
import type { SidecarCommand, SidecarEvent } from '../protocol';
import { initPaths, loadState, flushAndSave, acquireLock, releaseLock } from './state';
import { setMcpConfigPath } from './claude-manager';
import * as claudeManager from './claude-manager';
import { createSidecarConnector } from '../connect';
import { sign } from '../../jwt';
import { getUserByEmail, getEmailAccounts } from 'officerdb';

const email = process.env.CLAUDE_USER_EMAIL;
if (!email) {
  console.error('[user-instance] CLAUDE_USER_EMAIL is required');
  process.exit(1);
}

const DATA_PATH = process.env.DATA_PATH ?? join(process.cwd(), 'data');
const API_URL = process.env.API_URL ?? `ws://127.0.0.1:${process.env.PORT ?? '5000'}`;
const OFFICER_API_URL = process.env.OFFICER_API_URL ?? `http://127.0.0.1:${process.env.PORT ?? '9010'}`;
const MCP_SERVER_SCRIPT = resolve(import.meta.dir, '../../mcp-tool-server.ts');

// Mint a long-lived JWT for this user so tools (e.g. gmail) can call back to dev-platform as them
const dbUser = await getUserByEmail(email);
if (!dbUser) {
  console.error(`[user-instance] no user found for ${email}`);
  process.exit(1);
}
const OFFICER_AUTH_TOKEN = await sign({ id: dbUser.id, email, username: dbUser.username }, '30d');

// Single-user platform: the owner runs Claude with no isolation — real HOME, real ~/.claude — so
// platform sessions have perfect parity with terminal sessions (same config, credentials and
// transcript store, interchangeable via `claude --resume`).
const homeDir = process.env.HOME_DIR ?? homedir();
const globalToolsDir = join(DATA_PATH, 'tools');

// The email_db MCP tool reads one account's SQLite store; match the API's choice (first enabled).
const emailAccounts = await getEmailAccounts(dbUser.id);
const emailAccount = emailAccounts.find((a) => a.enabled) ?? emailAccounts[0];
const emailDbRel = join('email_accounts', emailAccount?.email ?? 'none', 'emails.db');
const userToolsDir = join(DATA_PATH, email, 'tools');

// ── Path setup ──

// Set HOME so claude inherits it
process.env.HOME = homeDir;

// Init per-user state paths
initPaths(email);

if (!acquireLock()) {
  console.error(`[claude:${email}] another instance is already running (lock file exists with live PID)`);
  process.exit(1);
}

loadState();

// ── MCP config ──

function generateMcpConfig(): string {
  const contextDir = join(DATA_PATH, email!, '.container-context');
  mkdirSync(contextDir, { recursive: true });

  const userRoot = join(DATA_PATH, email!);

  const hostToolsDirs = [globalToolsDir, ...(existsSync(userToolsDir) ? [userToolsDir] : [])].join(':');
  const hostConfig = {
    mcpServers: {
      'officer-tools': {
        type: 'stdio',
        command: 'bun',
        args: ['run', MCP_SERVER_SCRIPT],
        env: {
          PI_TOOLS_DIRS: hostToolsDirs,
          OFFICER_EMAIL_DB: join(userRoot, emailDbRel),
          MCP_TOOLS_LOG: join(userRoot, 'logs', 'mcp-tools.log'),
          OFFICER_API_URL,
          OFFICER_AUTH_TOKEN,
        },
      },
    },
  };
  writeFileSync(join(contextDir, 'mcp-host.json'), JSON.stringify(hostConfig));

  return join(contextDir, 'mcp-host.json');
}

// ── Startup ──

// The owner runs un-isolated with HOME as their real home, so the generated container CLAUDE.md is
// deliberately not written — it would pollute the personal global ~/.claude/CLAUDE.md that the
// terminal `claude` loads too.

setMcpConfigPath(generateMcpConfig());

console.log(`[claude:${email}] started (HOME=${homeDir})`);

// ── Command handlers ──

type ReplyFn = (msg: SidecarEvent) => void;

async function handleCommand(cmd: SidecarCommand, reply: ReplyFn) {
  switch (cmd.type) {
    case 'ping':
      reply({ type: 'pong', id: cmd.id });
      break;

    case 'claude:spawn': {
      try {
        const result = await claudeManager.spawnClaude(cmd.params);
        reply({ type: 'claude:result', id: cmd.id, result });
      } catch (err) {
        reply({ type: 'claude:error', id: cmd.id, error: err instanceof Error ? err.message : String(err) });
      }
      break;
    }

    case 'claude:spawn-streaming': {
      reply({ type: 'claude:spawned', id: cmd.id, sessionKey: cmd.params.sessionKey });

      const onEvent = (event: import('../../api/chat/types').ChatEvent) => {
        connection.send({ type: 'claude:event', sessionKey: cmd.params.sessionKey, event });
      };

      claudeManager.spawnClaudeStreaming(cmd.params, onEvent).catch((err) => {
        connection.send({
          type: 'claude:event',
          sessionKey: cmd.params.sessionKey,
          event: { type: 'error', message: err instanceof Error ? err.message : String(err) },
        });
      });
      break;
    }

    case 'claude:kill':
      claudeManager.killClaudeSession(cmd.sessionKey);
      reply({ type: 'claude:killed', id: cmd.id });
      break;

    case 'claude:interrupt':
      await claudeManager.interruptClaudeSession(cmd.sessionKey);
      reply({ type: 'claude:interrupted', id: cmd.id });
      break;

    case 'claude:clear-session':
      claudeManager.clearSession(cmd.sessionKey);
      reply({ type: 'claude:session-cleared', id: cmd.id });
      break;

    default:
      reply({
        type: 'error',
        id: (cmd as SidecarCommand).id,
        error: `Unknown command type: ${(cmd as Record<string, unknown>).type}`,
      });
  }
}

// ── Connect to API server ──

const connection = createSidecarConnector({
  apiUrl: `${API_URL}/api/sidecar/register`,
  name: `claude:${email}`,
  capabilities: ['claude'],
  onCommand(cmd, reply) {
    handleCommand(cmd as SidecarCommand, reply as ReplyFn);
  },
});

// ── Graceful shutdown ──

async function shutdown(signal: string) {
  console.log(`[claude:${email}] ${signal} received, saving state...`);
  connection.destroy();
  await flushAndSave();
  releaseLock();
  process.exit(0);
}

process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
