/**
 * dispatch-webhook skill
 * 發送 Webhook 到 VPS，處理重試與錯誤
 */

interface DispatchInput {
  target: 'vps-a' | 'vps-b';
  payload: Record<string, unknown>;
  webhookUrl: string;
  webhookToken: string;
  timeoutMs?: number;
  retries?: number;
}

const ALLOWED_TARGETS = new Set<DispatchInput['target']>(['vps-a', 'vps-b']);

function clampInt(value: unknown, min: number, max: number, fallback: number): number {
  const n = Number(value);
  if (!Number.isFinite(n)) return fallback;
  return Math.min(max, Math.max(min, Math.floor(n)));
}

function sanitizeTaskId(taskId: unknown): string {
  if (taskId == null) return '';
  return String(taskId).replace(/[\r\n]/g, '').slice(0, 128);
}

function validateInput(raw: any): DispatchInput {
  const input = raw as Partial<DispatchInput>;

  if (!input || typeof input !== 'object') {
    throw new Error('dispatch-webhook 輸入格式錯誤：必須提供 input 物件');
  }

  if (!input.target || !ALLOWED_TARGETS.has(input.target as DispatchInput['target'])) {
    throw new Error('dispatch-webhook 參數錯誤：target 必須是 vps-a 或 vps-b');
  }

  if (!input.webhookUrl || typeof input.webhookUrl !== 'string') {
    throw new Error(`${input.target.toUpperCase()} Webhook URL 未設定。請在環境變數設定 VPS_A_WEBHOOK_URL 或 VPS_B_WEBHOOK_URL`);
  }

  let parsedUrl: URL;
  try {
    parsedUrl = new URL(input.webhookUrl);
  } catch {
    throw new Error('Webhook URL 格式錯誤，請提供有效的 http/https URL');
  }

  if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
    throw new Error('Webhook URL 協定不支援，僅允許 http 或 https');
  }

  // 安全預設：正式環境僅允許 HTTPS，避免 Bearer Token 明文傳輸
  // 本機開發保留 http://localhost 與 http://127.0.0.1 例外
  const isLocalhost = ['localhost', '127.0.0.1'].includes(parsedUrl.hostname);
  if (parsedUrl.protocol !== 'https:' && !isLocalhost) {
    throw new Error('Webhook URL 安全性不足：非本機位址必須使用 https');
  }

  if (!input.webhookToken || typeof input.webhookToken !== 'string') {
    throw new Error(`${input.target.toUpperCase()} Webhook Token 未設定`);
  }

  if (!input.payload || typeof input.payload !== 'object' || Array.isArray(input.payload)) {
    throw new Error('dispatch-webhook 參數錯誤：payload 必須是 JSON 物件');
  }

  return input as DispatchInput;
}

async function fetchWithTimeout(url: string, options: RequestInit, timeoutMs: number): Promise<Response> {
  const controller = new AbortController();
  const timer = setTimeout(() => controller.abort(), timeoutMs);
  try {
    return await fetch(url, { ...options, signal: controller.signal });
  } finally {
    clearTimeout(timer);
  }
}

export async function handler(ctx: any) {
  const input = validateInput(ctx.input || ctx.params);

  const timeoutMs = clampInt(input.timeoutMs, 1000, 120000, 30000);
  const maxRetries = clampInt(input.retries, 1, 5, 3);
  const taskIdHeader = sanitizeTaskId(input.payload.task_id);
  let lastError: Error | null = null;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetchWithTimeout(
        input.webhookUrl,
        {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
            'Authorization': `Bearer ${input.webhookToken}`,
            'X-OpenClaw-Version': '1.0',
            'X-OpenClaw-Task-Id': taskIdHeader,
          },
          body: JSON.stringify(input.payload),
        },
        timeoutMs
      );

      if (response.status === 401) {
        throw new Error('Webhook Token 驗證失敗（401），請確認 VPS_WEBHOOK_TOKEN 設定正確');
      }

      if (!response.ok && response.status !== 202) {
        const body = await response.text().catch(() => '');
        throw new Error(`VPS 回應錯誤 ${response.status}：${body.slice(0, 200)}`);
      }

      const result = await response.json().catch(() => ({ status: 'accepted' }));
      return {
        success: true,
        status: result.status || 'accepted',
        task_id: input.payload.task_id,
        target: input.target,
        attempt,
      };

    } catch (err: any) {
      lastError = err instanceof Error ? err : new Error(String(err));
      if (lastError.message.includes('401') || lastError.message.includes('Token')) {
        break; // 認證錯誤不重試
      }
      if (attempt < maxRetries) {
        await new Promise(r => setTimeout(r, 5000 * attempt)); // 指數退避
      }
    }
  }

  throw lastError || new Error('Webhook 發送失敗');
}
