/**
 * `loxia composio …` CLI implementation. Extracted from bin/cli.js to
 * keep the main entry point readable. Talks to the REST endpoints in
 * webServer.js (which delegate to src/services/composioService.js).
 *
 * Subcommands:
 *   toolkits                       list available integrations
 *   connections                    list current connections
 *   status [<toolkit>]             overall or per-toolkit status
 *   connect <toolkit> [--open]     start OAuth flow
 *   disconnect <toolkit|conn-id>   revoke
 *   tools <toolkit>                list actions for a toolkit
 *   exec <toolkit> <action> [--args '{json}']
 *
 * Flags:
 *   --json     emit machine-readable JSON instead of pretty tables
 *   --user X   override the userId (defaults to "operator")
 *   --open     for `connect`: auto-launch the Connect Link in browser
 *   --help     show help
 */
import { exec } from 'child_process';

/* ------------------------------------------------------------------------ */
/* Output helpers — pretty when TTY, plain when piped, JSON when --json     */
/* ------------------------------------------------------------------------ */

const USE_COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
const C = USE_COLOR ? {
  dim:    s => `\x1b[2m${s}\x1b[0m`,
  bold:   s => `\x1b[1m${s}\x1b[0m`,
  red:    s => `\x1b[31m${s}\x1b[0m`,
  green:  s => `\x1b[32m${s}\x1b[0m`,
  yellow: s => `\x1b[33m${s}\x1b[0m`,
  blue:   s => `\x1b[34m${s}\x1b[0m`,
  magenta:s => `\x1b[35m${s}\x1b[0m`,
  cyan:   s => `\x1b[36m${s}\x1b[0m`,
  gray:   s => `\x1b[90m${s}\x1b[0m`,
} : {
  dim: x => x, bold: x => x, red: x => x, green: x => x,
  yellow: x => x, blue: x => x, magenta: x => x, cyan: x => x, gray: x => x,
};

function statusDot(status) {
  const s = String(status || '').toUpperCase();
  if (s === 'ACTIVE')    return C.green('●');
  if (s === 'INITIATED') return C.yellow('●');
  if (s === 'FAILED')    return C.red('●');
  return C.gray('○');
}

/** Print a simple table: rows are arrays of strings, header is the first row. */
function printTable(rows, opts = {}) {
  if (!rows || rows.length === 0) {
    console.log(C.gray(opts.empty || '(no rows)'));
    return;
  }
  const cols = rows[0].length;
  const widths = Array(cols).fill(0);
  for (const row of rows) {
    row.forEach((cell, i) => {
      const len = String(cell == null ? '' : cell).replace(/\x1b\[[0-9;]*m/g, '').length;
      if (len > widths[i]) widths[i] = len;
    });
  }
  rows.forEach((row, ri) => {
    const cells = row.map((cell, i) => {
      const str = cell == null ? '' : String(cell);
      const visible = str.replace(/\x1b\[[0-9;]*m/g, '').length;
      const pad = ' '.repeat(Math.max(0, widths[i] - visible));
      return str + pad;
    });
    const line = '  ' + cells.join('   ');
    console.log(ri === 0 ? C.bold(line) : line);
    if (ri === 0 && rows.length > 1) {
      const rule = widths.map(w => '─'.repeat(w)).join('   ');
      console.log('  ' + C.gray(rule));
    }
  });
}

function printJson(obj) {
  console.log(JSON.stringify(obj, null, 2));
}

/* ------------------------------------------------------------------------ */
/* Argument parsing                                                          */
/* ------------------------------------------------------------------------ */

function parseFlags(args) {
  const out = { positional: [], json: false, open: false, user: null, args: null, help: false };
  for (let i = 0; i < args.length; i++) {
    const a = args[i];
    if (a === '--json') out.json = true;
    else if (a === '--open') out.open = true;
    else if (a === '--user' && args[i + 1]) { out.user = args[++i]; }
    else if (a.startsWith('--user=')) { out.user = a.slice('--user='.length); }
    else if (a === '--args' && args[i + 1]) { out.args = args[++i]; }
    else if (a.startsWith('--args=')) { out.args = a.slice('--args='.length); }
    else if (a === '--help' || a === '-h') out.help = true;
    else out.positional.push(a);
  }
  return out;
}

function showHelp() {
  const bin = process.env.LOXIA_BIN || 'loxia';
  console.log(`
${C.bold(`${bin} composio`)} — manage Composio integrations from the terminal.

${C.bold('USAGE')}
  ${bin} composio toolkits                       List available integrations
  ${bin} composio connections                    List your current connections
  ${bin} composio status [<toolkit>]             Show overall or per-toolkit status
  ${bin} composio connect <toolkit> [--open]     Start an OAuth connection flow
  ${bin} composio disconnect <toolkit|conn-id>   Revoke a connection
  ${bin} composio tools <toolkit>                List actions a toolkit exposes
  ${bin} composio exec <toolkit> <action>        Invoke an action
         [--args '{"k":"v"}']

${C.bold('FLAGS')}
  --json         Emit machine-readable JSON (good for scripting)
  --user <id>    Override userId (default: "operator")
  --open         For "connect": auto-launch the Connect Link in your browser
  --help, -h     Show this help

${C.bold('EXAMPLES')}
  ${bin} composio toolkits
  ${bin} composio connect github --open
  ${bin} composio exec github GITHUB_LIST_REPOSITORIES_FOR_AUTHENTICATED_USER --args '{"per_page":3}'
`);
}

/* ------------------------------------------------------------------------ */
/* Server discovery + HTTP                                                   */
/* ------------------------------------------------------------------------ */

async function ensureBackend(deps) {
  const { discoverBackend, startServer, waitForServerAtPort, waitForServerWithDiscovery, checkServerRunning, explicitPort, host } = deps;
  let backend = await discoverBackend();
  if (backend) {
    const ok = await checkServerRunning(backend.host, backend.port);
    if (!ok) backend = null;
  }
  if (!backend) {
    console.error(C.gray('No running server found. Starting Loxia in background…'));
    startServer(true);
    if (explicitPort) backend = await waitForServerAtPort(host, explicitPort, 30000);
    else backend = await waitForServerWithDiscovery(30000);
    if (!backend) {
      console.error(C.red('Failed to start server within 30s.'));
      process.exit(1);
    }
  }
  return backend;
}

function httpReq(deps, method, path, body) {
  const { http } = deps;
  return new Promise((resolve, reject) => {
    const url = new URL(path, `http://${deps.backend.host}:${deps.backend.port}`);
    const data = body !== undefined ? JSON.stringify(body) : null;
    const req = http.request(url.toString(), {
      method,
      headers: {
        'content-type': 'application/json',
        ...(data ? { 'content-length': Buffer.byteLength(data) } : {}),
      },
    }, (res) => {
      let chunks = '';
      res.on('data', c => chunks += c);
      res.on('end', () => {
        try { resolve({ status: res.statusCode, body: chunks ? JSON.parse(chunks) : {} }); }
        catch { resolve({ status: res.statusCode, body: { raw: chunks } }); }
      });
    });
    req.on('error', reject);
    req.setTimeout(60000, () => { req.destroy(); reject(new Error('Request timed out')); });
    if (data) req.write(data);
    req.end();
  });
}

function openInBrowser(url) {
  const cmd = process.platform === 'win32' ? `start "" "${url}"`
    : process.platform === 'darwin' ? `open "${url}"`
    : `xdg-open "${url}"`;
  exec(cmd, () => {}); // Fire-and-forget; user can still copy the URL.
}

function maybeFailExit(result) {
  if (result.body && result.body.success === false) {
    console.error(C.red('Composio: ') + (result.body.error || 'unknown error'));
    // Friendly setup hints
    if (/COMPOSIO_API_KEY/.test(result.body.error || '')) {
      console.error(C.gray('  Set the env var:'));
      console.error(C.gray('    export COMPOSIO_API_KEY="<key-from-dashboard.composio.dev>"'));
    } else if (/@composio\/core/.test(result.body.error || '')) {
      console.error(C.gray('  Install the SDK:'));
      console.error(C.gray('    npm install @composio/core'));
    }
    process.exit(1);
  }
}

/* ------------------------------------------------------------------------ */
/* Subcommand handlers                                                       */
/* ------------------------------------------------------------------------ */

async function cmdToolkits(deps, flags) {
  const r = await httpReq(deps, 'GET', '/api/composio/toolkits');
  if (flags.json) return printJson(r.body);
  maybeFailExit(r);
  const toolkits = r.body.toolkits || [];
  if (toolkits.length === 0) {
    console.log(C.gray('No toolkits available. Enable some at https://dashboard.composio.dev.'));
    return;
  }
  const rows = [['SLUG', 'NAME', 'CATEGORIES']];
  for (const t of toolkits) {
    rows.push([
      C.cyan(t.slug || ''),
      t.name || '',
      (t.categories || t.tags || []).slice(0, 3).join(', '),
    ]);
  }
  printTable(rows);
  console.log(C.gray(`\n  ${toolkits.length} integrations available`));
}

async function cmdConnections(deps, flags) {
  const userId = flags.user || 'operator';
  const r = await httpReq(deps, 'GET', `/api/composio/connections?userId=${encodeURIComponent(userId)}`);
  if (flags.json) return printJson(r.body);
  maybeFailExit(r);
  const cs = r.body.connections || [];
  if (cs.length === 0) {
    console.log(C.gray(`No active connections for user "${userId}".`));
    console.log(C.gray('Run `loxia composio connect <toolkit>` to start one.'));
    return;
  }
  const rows = [['', 'TOOLKIT', 'STATUS', 'CONNECTION ID']];
  for (const c of cs) {
    rows.push([
      statusDot(c.status),
      C.cyan(c.toolkitSlug || c.toolkit || ''),
      c.status || '?',
      C.dim(c.id || ''),
    ]);
  }
  printTable(rows);
  console.log(C.gray(`\n  ${cs.length} connection${cs.length === 1 ? '' : 's'} for "${userId}"`));
}

async function cmdStatus(deps, flags) {
  const slug = flags.positional[1];
  if (!slug) {
    // Show overall availability + connection count
    const a = await httpReq(deps, 'GET', '/api/composio/availability');
    const c = await httpReq(deps, 'GET', `/api/composio/connections?userId=${encodeURIComponent(flags.user || 'operator')}`);
    if (flags.json) return printJson({ availability: a.body, connections: c.body });
    if (a.body.available) {
      console.log(`${C.green('●')} Composio ready`);
    } else {
      console.log(`${C.red('●')} Composio unavailable: ${a.body.reason || '(unknown)'}`);
    }
    if (c.body.success) {
      console.log(`  ${(c.body.connections || []).length} active connection(s) for "${flags.user || 'operator'}"`);
    }
    return;
  }
  const userId = flags.user || 'operator';
  const r = await httpReq(deps, 'GET', `/api/composio/toolkits/${encodeURIComponent(slug)}/status?userId=${encodeURIComponent(userId)}`);
  if (flags.json) return printJson(r.body);
  maybeFailExit(r);
  const label = r.body.connected ? C.green('Connected')
    : r.body.status === 'INITIATED' ? C.yellow('Connection in progress')
    : r.body.status === 'FAILED' ? C.red('Connection failed')
    : C.gray('Not connected');
  console.log(`  ${statusDot(r.body.status)} ${C.bold(slug)} · ${label}`);
  if (r.body.connectionId) console.log(C.gray(`    connection-id: ${r.body.connectionId}`));
}

async function cmdConnect(deps, flags) {
  const slug = flags.positional[1];
  if (!slug) {
    console.error(C.red('Usage:') + ` loxia composio connect <toolkit> [--open]`);
    process.exit(1);
  }
  const r = await httpReq(deps, 'POST', '/api/composio/connect', { toolkitSlug: slug, userId: flags.user || 'operator' });
  if (flags.json) return printJson(r.body);
  maybeFailExit(r);
  const link = r.body.connectLink;
  if (!link) {
    console.error(C.red('No Connect Link returned. Check the toolkit is enabled at dashboard.composio.dev.'));
    process.exit(1);
  }
  console.log(`  ${C.bold('Connect ' + slug + ':')}  ${C.cyan(link)}`);
  if (r.body.connectionId) console.log(C.gray(`  connection-id: ${r.body.connectionId}`));
  if (flags.open) {
    console.log(C.gray('  → opening in your default browser'));
    openInBrowser(link);
  } else {
    console.log(C.gray('  Open the URL in a browser to complete OAuth. (Use --open to launch automatically.)'));
  }
}

async function cmdDisconnect(deps, flags) {
  const target = flags.positional[1];
  if (!target) {
    console.error(C.red('Usage:') + ` loxia composio disconnect <toolkit-or-connection-id>`);
    process.exit(1);
  }
  // Heuristic: connection IDs look like UUIDs / hex / contain dashes-and-no-spaces.
  // Toolkit slugs are short word-with-underscore strings.
  const looksLikeId = /[-]/.test(target) && target.length > 16;
  let r;
  if (looksLikeId) {
    r = await httpReq(deps, 'DELETE', `/api/composio/connections/${encodeURIComponent(target)}`);
  } else {
    r = await httpReq(deps, 'DELETE', `/api/composio/connections/by-toolkit?userId=${encodeURIComponent(flags.user || 'operator')}`, {
      toolkitSlug: target,
    });
  }
  if (flags.json) return printJson(r.body);
  maybeFailExit(r);
  console.log(`${C.green('✓')} Disconnected ${C.bold(target)}`);
}

async function cmdTools(deps, flags) {
  const slug = flags.positional[1];
  if (!slug) {
    console.error(C.red('Usage:') + ` loxia composio tools <toolkit>`);
    process.exit(1);
  }
  const r = await httpReq(deps, 'GET', `/api/composio/toolkits/${encodeURIComponent(slug)}/tools`);
  if (flags.json) return printJson(r.body);
  maybeFailExit(r);
  const tools = r.body.tools || [];
  if (tools.length === 0) {
    console.log(C.gray(`No actions available for ${slug}.`));
    return;
  }
  for (const t of tools) {
    console.log(`  ${C.cyan(t.slug || t.name || '')}`);
    const desc = t.displayName || t.description;
    if (desc) console.log(`    ${C.gray(String(desc).split('\n')[0])}`);
  }
  console.log(C.gray(`\n  ${tools.length} action${tools.length === 1 ? '' : 's'} on ${slug}`));
}

async function cmdExec(deps, flags) {
  const slug = flags.positional[1];
  const actionSlug = flags.positional[2];
  if (!slug || !actionSlug) {
    console.error(C.red('Usage:') + ` loxia composio exec <toolkit> <action> [--args '{"k":"v"}']`);
    process.exit(1);
  }
  let args = {};
  if (flags.args) {
    try { args = JSON.parse(flags.args); }
    catch (e) {
      console.error(C.red('Invalid --args JSON: ') + e.message);
      process.exit(1);
    }
  }
  const r = await httpReq(deps, 'POST', '/api/composio/execute', {
    toolkitSlug: slug,
    actionSlug,
    arguments: args,
    userId: flags.user || 'operator',
  });
  if (flags.json) return printJson(r.body);
  maybeFailExit(r);
  if (r.body.success) {
    console.log(`${C.green('✓')} ${slug} · ${C.bold(actionSlug)}`);
    if (r.body.output != null) {
      const out = typeof r.body.output === 'object'
        ? JSON.stringify(r.body.output, null, 2)
        : String(r.body.output);
      console.log(out.split('\n').map(line => '  ' + line).join('\n'));
    }
  } else {
    console.log(`${C.red('✗')} ${slug} · ${actionSlug}`);
    if (r.body.error) console.log(C.red('  ' + r.body.error));
    process.exit(1);
  }
}

/* ------------------------------------------------------------------------ */
/* Top-level dispatcher                                                      */
/* ------------------------------------------------------------------------ */

export async function composioCommand(deps) {
  const flags = parseFlags(deps.args.slice(1)); // strip "composio"
  if (flags.help || flags.positional.length === 0) {
    showHelp();
    process.exit(flags.help ? 0 : 1);
  }
  const sub = flags.positional[0];

  // Ensure a backend is reachable before any subcommand that hits REST.
  const backend = await ensureBackend(deps);
  deps.backend = backend;

  switch (sub) {
    case 'toolkits':     return cmdToolkits(deps, flags);
    case 'connections':  return cmdConnections(deps, flags);
    case 'status':       return cmdStatus(deps, flags);
    case 'connect':      return cmdConnect(deps, flags);
    case 'disconnect':   return cmdDisconnect(deps, flags);
    case 'tools':        return cmdTools(deps, flags);
    case 'exec':
    case 'execute':      return cmdExec(deps, flags);
    case 'help':
      showHelp();
      process.exit(0);
      return;
    default:
      console.error(C.red(`Unknown subcommand: ${sub}`));
      console.error(`Run \`loxia composio --help\` for usage.`);
      process.exit(1);
  }
}

/* Exposed for unit tests — production callers go via `composioCommand`. */
export const _internal = {
  parseFlags,
  statusDot,
  printTable: (...a) => printTable(...a),
  cmdToolkits, cmdConnections, cmdStatus, cmdConnect, cmdDisconnect, cmdTools, cmdExec,
};
