#!/usr/bin/env node
/*
  Google Workspace helper CLI
  Commands:
    whoami
    send --to <email> --subject <text> --body <text> [--html] [--attach <file>]
    search-mail --query <gmail query> [--max 10]
    search-calendar --query <text> [--max 10] [--timeMin ISO] [--timeMax ISO] [--calendar primary]
    create-event --summary <text> --start <ISO> --end <ISO> [--timeZone America/Chicago] [--description <text>] [--location <text>] [--calendar primary]
*/

const fs = require('fs');
const path = require('path');

const DEFAULT_SUBJECT = process.env.GW_IMPERSONATE || 'stefano@fiorinis.com';
const DEFAULT_KEY_CANDIDATES = [
  process.env.GW_KEY_PATH,
  path.join(process.env.HOME || '', '.openclaw/workspace/.clawdbot/credentials/google-workspace/service-account.json'),
  path.join(process.env.HOME || '', '.clawdbot/credentials/google-workspace/service-account.json'),
].filter(Boolean);

const SCOPES = [
  'https://www.googleapis.com/auth/gmail.send',
  'https://www.googleapis.com/auth/gmail.compose',
  'https://www.googleapis.com/auth/gmail.readonly',
  'https://www.googleapis.com/auth/calendar',
  'https://www.googleapis.com/auth/calendar.events',
];

function resolveKeyPath() {
  for (const p of DEFAULT_KEY_CANDIDATES) {
    if (p && fs.existsSync(p)) return p;
  }
  return null;
}

function parseArgs(argv) {
  const out = { _: [] };
  for (let i = 0; i < argv.length; i++) {
    const token = argv[i];
    if (token.startsWith('--')) {
      const key = token.slice(2);
      const next = argv[i + 1];
      if (!next || next.startsWith('--')) {
        out[key] = true;
      } else {
        if (Object.hasOwn(out, key)) {
          out[key] = Array.isArray(out[key]) ? out[key].concat(next) : [out[key], next];
        } else {
          out[key] = next;
        }
        i++;
      }
    } else {
      out._.push(token);
    }
  }
  return out;
}

function usage() {
  console.log(`Google Workspace CLI\n
Env (optional):
  GW_IMPERSONATE   user to impersonate (default: ${DEFAULT_SUBJECT})
  GW_KEY_PATH      service-account key path

Commands:
  whoami
  send --to <email> --subject <text> --body <text> [--html] [--attach <file>]
  search-mail --query <gmail query> [--max 10]
  search-calendar --query <text> [--max 10] [--timeMin ISO] [--timeMax ISO] [--calendar primary]
  create-event --summary <text> --start <ISO> --end <ISO> [--timeZone America/Chicago] [--description <text>] [--location <text>] [--calendar primary]
`);
}

function assertRequired(opts, required) {
  const missing = required.filter((k) => !opts[k]);
  if (missing.length) {
    throw new Error(`Missing required options: ${missing.map((m) => `--${m}`).join(', ')}`);
  }
}

function toArray(value) {
  if (value == null || value === false) return [];
  return Array.isArray(value) ? value : [value];
}

function getAttachmentContentType(filename) {
  const ext = path.extname(filename).toLowerCase();
  if (ext === '.pdf') return 'application/pdf';
  if (ext === '.txt') return 'text/plain; charset="UTF-8"';
  if (ext === '.html' || ext === '.htm') return 'text/html; charset="UTF-8"';
  if (ext === '.json') return 'application/json';
  return 'application/octet-stream';
}

function wrapBase64(base64) {
  return base64.match(/.{1,76}/g)?.join('\r\n') || '';
}

function loadAttachments(attachArg) {
  return toArray(attachArg).map((filePath) => {
    const absolutePath = path.resolve(filePath);
    if (!fs.existsSync(absolutePath)) {
      throw new Error(`Attachment file not found: ${absolutePath}`);
    }

    return {
      filename: path.basename(absolutePath),
      contentType: getAttachmentContentType(absolutePath),
      data: fs.readFileSync(absolutePath).toString('base64'),
    };
  });
}

function makeRawEmail({ from, to, subject, body, isHtml = false, attachments = [] }) {
  const contentType = isHtml ? 'text/html; charset="UTF-8"' : 'text/plain; charset="UTF-8"';
  const normalizedAttachments = attachments.filter(Boolean);

  let msg;
  if (normalizedAttachments.length === 0) {
    msg = [
      `From: ${from}`,
      `To: ${to}`,
      `Subject: ${subject}`,
      'MIME-Version: 1.0',
      `Content-Type: ${contentType}`,
      '',
      body,
    ].join('\r\n');
  } else {
    const boundary = `gw-boundary-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
    msg = [
      `From: ${from}`,
      `To: ${to}`,
      `Subject: ${subject}`,
      'MIME-Version: 1.0',
      `Content-Type: multipart/mixed; boundary="${boundary}"`,
      '',
      `--${boundary}`,
      `Content-Type: ${contentType}`,
      'Content-Transfer-Encoding: 7bit',
      '',
      body,
      '',
      ...normalizedAttachments.flatMap((attachment) => [
        `--${boundary}`,
        `Content-Type: ${attachment.contentType}; name="${attachment.filename}"`,
        'Content-Transfer-Encoding: base64',
        `Content-Disposition: attachment; filename="${attachment.filename}"`,
        '',
        wrapBase64(attachment.data),
        '',
      ]),
      `--${boundary}--`,
      '',
    ].join('\r\n');
  }

  return Buffer.from(msg)
    .toString('base64')
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/g, '');
}

async function getClients() {
  const { google } = require('googleapis');
  const keyPath = resolveKeyPath();
  if (!keyPath) {
    throw new Error('Service account key not found. Set GW_KEY_PATH or place the file in ~/.openclaw/workspace/.clawdbot/credentials/google-workspace/service-account.json');
  }

  const auth = new google.auth.GoogleAuth({
    keyFile: keyPath,
    scopes: SCOPES,
    clientOptions: { subject: DEFAULT_SUBJECT },
  });

  const authClient = await auth.getClient();
  const gmail = google.gmail({ version: 'v1', auth: authClient });
  const calendar = google.calendar({ version: 'v3', auth: authClient });

  return { gmail, calendar, keyPath };
}

async function cmdWhoami(clients) {
  const profile = await clients.gmail.users.getProfile({ userId: 'me' });
  console.log(JSON.stringify({
    impersonating: DEFAULT_SUBJECT,
    keyPath: clients.keyPath,
    profile: profile.data,
  }, null, 2));
}

async function cmdSend(clients, opts) {
  assertRequired(opts, ['to', 'subject', 'body']);
  const raw = makeRawEmail({
    from: DEFAULT_SUBJECT,
    to: opts.to,
    subject: opts.subject,
    body: opts.body,
    isHtml: !!opts.html,
    attachments: loadAttachments(opts.attach),
  });

  const res = await clients.gmail.users.messages.send({
    userId: 'me',
    requestBody: { raw },
  });

  console.log(JSON.stringify({ ok: true, id: res.data.id, threadId: res.data.threadId }, null, 2));
}

async function cmdSearchMail(clients, opts) {
  assertRequired(opts, ['query']);
  const maxResults = Math.max(1, Math.min(50, Number(opts.max || 10)));

  const list = await clients.gmail.users.messages.list({
    userId: 'me',
    q: opts.query,
    maxResults,
  });

  const ids = (list.data.messages || []).map((m) => m.id).filter(Boolean);
  const out = [];
  for (const id of ids) {
    const msg = await clients.gmail.users.messages.get({
      userId: 'me',
      id,
      format: 'metadata',
      metadataHeaders: ['From', 'To', 'Subject', 'Date'],
    });
    const headers = Object.fromEntries((msg.data.payload?.headers || []).map((h) => [h.name, h.value]));
    out.push({
      id,
      threadId: msg.data.threadId,
      snippet: msg.data.snippet,
      from: headers.From,
      to: headers.To,
      subject: headers.Subject,
      date: headers.Date,
    });
  }

  console.log(JSON.stringify({ count: out.length, messages: out }, null, 2));
}

async function cmdSearchCalendar(clients, opts) {
  const maxResults = Math.max(1, Math.min(50, Number(opts.max || 10)));

  const res = await clients.calendar.events.list({
    calendarId: opts.calendar || 'primary',
    q: opts.query || undefined,
    timeMin: opts.timeMin,
    timeMax: opts.timeMax,
    singleEvents: true,
    orderBy: 'startTime',
    maxResults,
  });

  const events = (res.data.items || []).map((e) => ({
    id: e.id,
    summary: e.summary,
    status: e.status,
    start: e.start,
    end: e.end,
    location: e.location,
    hangoutLink: e.hangoutLink,
  }));

  console.log(JSON.stringify({ count: events.length, events }, null, 2));
}

async function cmdCreateEvent(clients, opts) {
  assertRequired(opts, ['summary', 'start', 'end']);
  const tz = opts.timeZone || 'America/Chicago';

  const event = {
    summary: opts.summary,
    description: opts.description,
    location: opts.location,
    start: { dateTime: opts.start, timeZone: tz },
    end: { dateTime: opts.end, timeZone: tz },
  };

  const res = await clients.calendar.events.insert({
    calendarId: opts.calendar || 'primary',
    requestBody: event,
  });

  console.log(JSON.stringify({ ok: true, id: res.data.id, htmlLink: res.data.htmlLink }, null, 2));
}

async function main() {
  try {
    const args = parseArgs(process.argv.slice(2));
    const cmd = args._[0];

    if (!cmd || cmd === 'help' || cmd === '--help' || cmd === '-h') {
      usage();
      process.exit(0);
    }

    const clients = await getClients();

    if (cmd === 'whoami') return await cmdWhoami(clients);
    if (cmd === 'send') return await cmdSend(clients, args);
    if (cmd === 'search-mail') return await cmdSearchMail(clients, args);
    if (cmd === 'search-calendar') return await cmdSearchCalendar(clients, args);
    if (cmd === 'create-event') return await cmdCreateEvent(clients, args);

    throw new Error(`Unknown command: ${cmd}`);
  } catch (err) {
    console.error(`ERROR: ${err.message}`);
    process.exit(1);
  }
}

if (require.main === module) {
  main();
}

module.exports = {
  getAttachmentContentType,
  loadAttachments,
  main,
  makeRawEmail,
  parseArgs,
  resolveKeyPath,
  toArray,
  wrapBase64,
};
