'use strict';

const crypto = require('node:crypto');

const EXTERNAL_ACTION_TOOLS = new Set([
  'calendar_create',
  'mail_reply',
  'mail_send',
  'notification',
  'reminder_create',
  'slack_send_message',
  'sms_send',
]);

const DISPATCH_RE = /\b(?:send|sent|post|publish|text|sms|message|email|mail|notify|invite|schedule|book|create(?:\s+(?:an?\s+)?(?:event|reminder|notification)))\b/i;
const PREPARE_ONLY_RE = /\b(?:draft|compose|write|prepare|wordsmith|mock\s*up|help\s+me\s+(?:draft|write|compose|prepare))\b/i;
const IMMEDIATE_RE = /\b(?:now|right\s+now|immediately|go\s+ahead|please\s+send|send\s+it|post\s+it|do\s+it)\b/i;
const CALL_INVITATION_RE = /\b(?:call me|give me a call|call at your convenience|phone me|reach me by phone|call my phone)\b/i;
const PHONE_NUMBER_RE = /\b(?:\+?1[-.\s]?)?(?:\(?[2-9]\d{2}\)?[-.\s]?)?[2-9]\d{2}[-.\s]?\d{4}\b/;
const EMAIL_REPLY_INTENT_RE = /\b(?:reply|respond|response)\b[\s\S]{0,80}\b(?:email|mail|message|thread|original)\b|\b(?:email|mail|message|thread|original)\b[\s\S]{0,80}\b(?:reply|respond|response)\b/i;

function isExternalActionTool(toolName) {
  return EXTERNAL_ACTION_TOOLS.has(String(toolName || '').trim());
}

function reviewExternalAction({ toolName, input = {}, userMessage = '', approvals = null, now = () => new Date() } = {}) {
  if (!isExternalActionTool(toolName)) return { admitted: true };

  const envelope = buildExternalActionEnvelope({ toolName, input, userMessage, now });
  const approval = findApprovalForEnvelope(envelope, approvals);
  if (approval) {
    return {
      admitted: true,
      envelope: {
        ...envelope,
        approval: {
          id: approval.id || approval.approvalId || null,
          source: approval.source || 'external_action_approval',
        },
      },
    };
  }

  const decision = envelope.intent.kind === 'prepare_only'
    ? 'stage_preview'
    : 'requires_confirmation';

  return {
    admitted: false,
    envelope,
    result: buildBlockedToolResult(envelope, decision),
  };
}

function buildExternalActionEnvelope({ toolName, input = {}, userMessage = '', now = () => new Date() } = {}) {
  const domain = domainForTool(toolName);
  const intent = inferIntent({ userMessage, domain });
  const actor = actorForTool(toolName, input);
  const target = targetForTool(toolName, input);
  const payload = payloadForTool(toolName, input);
  const validation = validateEnvelope({ toolName, input, intent, actor, target, payload, userMessage });
  const payloadHash = hashStable({ domain, toolName, actor, target, payload });

  return {
    version: 1,
    id: `external_action_${payloadHash.slice(0, 16)}`,
    domain,
    toolName,
    operation: operationForTool(toolName),
    createdAt: now().toISOString(),
    intent,
    actor,
    target,
    payload,
    validation,
    risk: riskForTool(toolName, validation),
    idempotencyKey: `${domain}:${payloadHash}`,
    payloadHash,
  };
}

function inferIntent({ userMessage = '', domain = '' } = {}) {
  const text = String(userMessage || '');
  const prepareOnly = PREPARE_ONLY_RE.test(text) && !(DISPATCH_RE.test(text) && IMMEDIATE_RE.test(text));
  if (prepareOnly) {
    return {
      kind: 'prepare_only',
      confidence: 'high',
      evidence: 'The user asked to draft/compose/prepare content rather than dispatch it.',
    };
  }
  if (DISPATCH_RE.test(text)) {
    return {
      kind: 'dispatch_requested',
      confidence: IMMEDIATE_RE.test(text) ? 'medium' : 'low',
      evidence: `The user used ${domain || 'external'} action language, but execution still requires validation and confirmation.`,
    };
  }
  return {
    kind: 'ambiguous',
    confidence: 'low',
    evidence: 'The user intent did not clearly authorize an external side effect.',
  };
}

function actorForTool(toolName, input = {}) {
  if (toolName === 'mail_send' || toolName === 'mail_reply') {
    const parsed = parseGmailAccountFromMessageRef(input.message_id || input.reply_to_message_id);
    return {
      type: 'email_account',
      account: input.account || parsed?.email || null,
      status: input.account || parsed?.email ? 'resolved_from_tool_input' : 'missing',
    };
  }
  if (toolName === 'slack_send_message') {
    return { type: 'slack_identity', account: input.account || null, status: 'runtime_default' };
  }
  return { type: 'runtime_identity', account: input.account || null, status: input.account ? 'resolved_from_tool_input' : 'runtime_default' };
}

function targetForTool(toolName, input = {}) {
  if (toolName === 'mail_send') {
    return { to: normalizeAddressList(input.to), cc: normalizeAddressList(input.cc), bcc: normalizeAddressList(input.bcc) };
  }
  if (toolName === 'mail_reply') {
    return {
      messageId: input.message_id || input.reply_to_message_id || input.replyToMessageId || null,
      cc: normalizeAddressList(input.cc),
      bcc: normalizeAddressList(input.bcc),
    };
  }
  if (toolName === 'slack_send_message') {
    return { channel: input.channel_name || input.channel || null };
  }
  if (toolName === 'calendar_create') {
    return {
      calendar: input.calendar || input.calendar_name || input.calendarId || input.calendar_id || null,
      calendarId: input.calendarId || input.calendar_id || null,
      account: input.account || null,
      source: input.source || input.provider || null,
      attendees: normalizeAddressList(input.attendees),
    };
  }
  if (toolName === 'reminder_create') {
    return { list: input.list || null };
  }
  if (toolName === 'notification') {
    return { device: input.device || 'local' };
  }
  return { target: input.to || input.target || null };
}

function payloadForTool(toolName, input = {}) {
  if (toolName === 'mail_send') {
    return { subject: input.subject || '', body: input.body || '', attachments: input.attachments || [] };
  }
  if (toolName === 'mail_reply') {
    return {
      subject: input.subject || '',
      body: input.body || '',
      attachments: input.attachments || [],
    };
  }
  if (toolName === 'slack_send_message') {
    return { message: input.message || '' };
  }
  if (toolName === 'calendar_create') {
    return {
      title: input.title || '',
      start: calendarStart(input),
      end: calendarEnd(input),
      location: input.location || '',
      notes: input.notes || '',
    };
  }
  if (toolName === 'reminder_create') {
    return { title: input.title || '', due: input.due || input.dueDate || null, notes: input.notes || '' };
  }
  if (toolName === 'notification') {
    return { title: input.title || '', message: input.message || '' };
  }
  return { ...input };
}

function inputForExternalActionEnvelope(envelope = {}) {
  const toolName = String(envelope.toolName || '').trim();
  const actor = envelope.actor || {};
  const target = envelope.target || {};
  const payload = envelope.payload || {};

  if (toolName === 'mail_send') {
    return {
      account: actor.account || null,
      to: normalizeAddressList(target.to),
      cc: normalizeAddressList(target.cc),
      bcc: normalizeAddressList(target.bcc),
      subject: payload.subject || '',
      body: payload.body || '',
      attachments: Array.isArray(payload.attachments) ? payload.attachments : [],
    };
  }

  if (toolName === 'mail_reply') {
    return {
      account: actor.account || null,
      message_id: target.messageId || null,
      cc: normalizeAddressList(target.cc),
      bcc: normalizeAddressList(target.bcc),
      subject: payload.subject || '',
      body: payload.body || '',
      attachments: Array.isArray(payload.attachments) ? payload.attachments : [],
    };
  }

  if (toolName === 'calendar_create') {
    return {
      calendar: target.calendar || null,
      calendarId: target.calendarId || null,
      account: actor.account || target.account || null,
      source: target.source || null,
      attendees: normalizeAddressList(target.attendees),
      title: payload.title || '',
      start_date: payload.start || null,
      end_date: payload.end || null,
      location: payload.location || '',
      notes: payload.notes || '',
    };
  }

  if (toolName === 'slack_send_message') {
    return {
      channel: target.channel || null,
      message: payload.message || '',
      account: actor.account || null,
    };
  }

  if (toolName === 'reminder_create') {
    return {
      list: target.list || null,
      title: payload.title || '',
      due: payload.due || null,
      notes: payload.notes || '',
    };
  }

  if (toolName === 'notification') {
    return {
      device: target.device || 'local',
      title: payload.title || '',
      message: payload.message || '',
    };
  }

  return { ...payload };
}

function calendarStart(input = {}) {
  return input.start ?? input.start_date ?? input.startDate ?? null;
}

function calendarEnd(input = {}) {
  return input.end ?? input.end_date ?? input.endDate ?? null;
}

function validateEnvelope({ toolName, input = {}, intent, actor, target, payload, userMessage = '' }) {
  const issues = [];
  const confirmations = [];

  if (toolName === 'mail_send') {
    if (EMAIL_REPLY_INTENT_RE.test(userMessage)) {
      issues.push(issue(
        'reply_requires_mail_reply',
        'The user asked to respond to an existing email. Use mail_reply with the original message_id so Gmail preserves the thread and derives recipients from the source message.'
      ));
    }
    if (!target.to.length) issues.push(issue('missing_target', 'Email has no recipient.'));
    if (!payload.subject.trim()) issues.push(issue('missing_subject', 'Email has no subject.'));
    if (!payload.body.trim()) issues.push(issue('missing_body', 'Email has no body.'));
    if (!actor.account) confirmations.push(issue('sender_account_unresolved', 'Sender email account must be selected before sending.'));
    if (CALL_INVITATION_RE.test(payload.body) && !PHONE_NUMBER_RE.test(payload.body)) {
      confirmations.push(issue('call_invitation_without_phone', 'Email invites a phone call but does not include a phone number.'));
    }
  } else if (toolName === 'mail_reply') {
    if (!target.messageId) issues.push(issue('missing_reply_source', 'Email reply has no source message_id.'));
    if (!payload.body.trim()) issues.push(issue('missing_body', 'Email reply has no body.'));
    if (!actor.account) confirmations.push(issue('sender_account_unresolved', 'Sender email account must be resolved from the Gmail message_id or selected before replying.'));
  } else if (toolName === 'slack_send_message') {
    if (!target.channel) issues.push(issue('missing_target', 'Slack message has no channel or DM target.'));
    if (!payload.message.trim()) issues.push(issue('missing_body', 'Slack message has no content.'));
  } else if (toolName === 'calendar_create') {
    if (!payload.title.trim()) issues.push(issue('missing_title', 'Calendar event has no title.'));
    if (!payload.start) issues.push(issue('missing_start', 'Calendar event has no start time.'));
    if (target.calendar) {
      confirmations.push(issue(
        'calendar_target_requires_resolution',
        'Calendar names must be resolved against live Google/macOS calendars before creation; execution will fail safely with available calendars if the name is missing or ambiguous.'
      ));
    }
  } else if (toolName === 'reminder_create') {
    if (!payload.title.trim()) issues.push(issue('missing_title', 'Reminder has no title.'));
  } else if (toolName === 'notification') {
    if (!payload.title.trim() && !payload.message.trim()) issues.push(issue('missing_body', 'Notification has no title or message.'));
  }

  if (intent.kind !== 'prepare_only') {
    confirmations.push(issue('external_action_confirmation_required', 'External side effects require a validated preview and explicit confirmation.'));
  }

  return {
    ok: issues.length === 0,
    issues,
    confirmations,
  };
}

function buildBlockedToolResult(envelope, decision) {
  const isPreview = decision === 'stage_preview';
  return {
    external_action: true,
    blocked: true,
    executed: false,
    sent: false,
    decision,
    reason: isPreview
      ? 'External action was staged because the user asked for prepared content, not dispatch.'
      : 'External action requires validation and explicit confirmation before execution.',
    action: envelope,
    approval_options: isPreview
      ? []
      : [
        { id: 'approve_once', label: 'Approve once', scope: 'exact_action_payload' },
        { id: 'cancel', label: 'Cancel', scope: 'exact_action_payload' },
      ],
    approval_policy: {
      kind: 'external_action',
      allow_always: false,
      approval_scope: 'exact_action_payload',
    },
    draft: envelope.domain === 'email'
      ? {
        to: envelope.toolName === 'mail_reply' ? 'derived_from_original_message' : envelope.target.to,
        cc: envelope.target.cc,
        bcc: envelope.target.bcc,
        account: envelope.actor.account,
        reply_to_message_id: envelope.toolName === 'mail_reply' ? envelope.target.messageId : undefined,
        subject: envelope.payload.subject,
        body: envelope.payload.body,
      }
      : undefined,
    model_instruction: [
      'Do not claim this external action was executed.',
      'Do not say you lack the ability to send email or create calendar events; say this action is staged and needs explicit confirmation before execution.',
      envelope.toolName === 'mail_reply'
        ? 'Present the email body as a reply draft anchored to the original message; do not invent or rewrite recipients.'
        : envelope.domain === 'email' ? 'Present the email body as a draft for review.' : 'Present the staged action for review.',
      'Mention validation issues or confirmations that should be resolved before dispatch.',
    ].join(' '),
  };
}

function findApprovalForEnvelope(envelope, approvals) {
  if (!approvals) return null;
  const list = Array.isArray(approvals)
    ? approvals
    : approvals instanceof Map
    ? [...approvals.values()]
    : Object.values(approvals);
  return list.find((approval) => {
    if (!approval) return false;
    const approvedHash = approval.payloadHash || approval.payload_hash;
    const approvedId = approval.actionId || approval.action_id || approval.id;
    return approval.decision === 'allow'
      && approvedHash === envelope.payloadHash
      && (!approvedId || approvedId === envelope.id);
  }) || list.find((approval) => {
    if (!approval || approval.decision !== 'allow') return false;
    const scope = approval.scope || approval.approvalScope || approval.approval_scope;
    if (scope !== 'current_turn_external_actions') return false;
    const domains = approval.domains || approval.allowedDomains || approval.allowed_domains || [];
    if (Array.isArray(domains) && domains.length && !domains.includes(envelope.domain)) return false;
    const toolNames = approval.toolNames || approval.allowedTools || approval.allowed_tools || [];
    if (Array.isArray(toolNames) && toolNames.length && !toolNames.includes(envelope.toolName)) return false;
    const issues = envelope.validation?.issues || [];
    if (issues.length > 0) return false;
    const confirmations = envelope.validation?.confirmations || [];
    return confirmations.every((issue) => issue.code === 'external_action_confirmation_required');
  }) || null;
}

function issue(code, message) {
  return { code, message };
}

function domainForTool(toolName) {
  if (toolName === 'mail_send' || toolName === 'mail_reply') return 'email';
  if (toolName === 'slack_send_message') return 'slack';
  if (toolName === 'sms_send') return 'sms';
  if (toolName === 'calendar_create') return 'calendar';
  if (toolName === 'reminder_create') return 'reminder';
  if (toolName === 'notification') return 'notification';
  return 'external';
}

function operationForTool(toolName) {
  if (toolName === 'mail_send') return 'send_email';
  if (toolName === 'mail_reply') return 'reply_email';
  if (toolName === 'slack_send_message') return 'send_message';
  if (toolName === 'sms_send') return 'send_sms';
  if (toolName === 'calendar_create') return 'create_event';
  if (toolName === 'reminder_create') return 'create_reminder';
  if (toolName === 'notification') return 'send_notification';
  return toolName;
}

function riskForTool(toolName, validation) {
  const externalWrite = isExternalActionTool(toolName);
  if (!externalWrite) return 'low';
  if ((validation.issues || []).length > 0) return 'high';
  if ((validation.confirmations || []).length > 0) return 'medium';
  return 'medium';
}

function normalizeAddressList(value) {
  if (!value) return [];
  if (Array.isArray(value)) return value.map(String).map(s => s.trim()).filter(Boolean);
  return String(value).split(',').map(s => s.trim()).filter(Boolean);
}

function parseGmailAccountFromMessageRef(messageId) {
  const match = String(messageId || '').match(/^gmail:([^:]+):.+$/i);
  if (!match) return null;
  return { email: match[1].toLowerCase() };
}

function hashStable(value) {
  return crypto.createHash('sha256').update(stableStringify(value)).digest('hex');
}

function stableStringify(value) {
  if (value === null || typeof value !== 'object') return JSON.stringify(value);
  if (Array.isArray(value)) return `[${value.map(stableStringify).join(',')}]`;
  const keys = Object.keys(value).sort();
  return `{${keys.map((key) => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
}

module.exports = {
  EXTERNAL_ACTION_TOOLS,
  buildExternalActionEnvelope,
  inputForExternalActionEnvelope,
  isExternalActionTool,
  reviewExternalAction,
};
