/**
 * ═══════════════════════════════════════════════════════════════════════════════
 * LOOPUMAN ENTERPRISE API v4.2
 * The Human Layer for AI — REST API for AI Agents & Enterprise
 * ═══════════════════════════════════════════════════════════════════════════════
 * 
 * FLOWS:
 * 1. SINGLE TASK (async): POST /api/v1/tasks → webhook fires when completed
 * 2. SINGLE TASK (sync):  POST /api/v1/tasks/sync → blocks until human responds
 * 3. BULK UPLOAD:         POST /api/v1/tasks/bulk → webhooks fire per task
 * 4. APPROVE/REJECT:      POST /api/v1/submissions/:id/approve|reject
 * 5. POLL:                GET /api/v1/tasks/:id, GET /api/v1/batches/:id
 * 
 * AUTH: x-api-key header (from api_keys table)
 * PORT: 3001 (proxied via api.loopuman.com)
 * 
 * FIXES in v4.0:
 * - Atomic balance operations (prevents double-spend)
 * - Sync endpoint now deducts balance
 * - Webhook delivery for ALL task types (not just batch)
 * - Content moderation on API tasks
 * - Single async task endpoint
 * - API-based approve/reject submissions
 * - Retry logic for webhooks
 * ═══════════════════════════════════════════════════════════════════════════════
 */

'use strict';
require('dotenv').config();

const express = require('express');
const { createClient } = require('@supabase/supabase-js');
const crypto = require('crypto');

const app = express();
app.use(express.static('public'));
app.use(express.json({ limit: '10mb' }));

// ═══ x402 PAYMENT PROTOCOL — Pay-per-call for AI Agents ═══
const { paymentMiddleware } = require('x402-express');

app.use(paymentMiddleware(
  "0x9F52C166c90567B7F9906c6319ef90aF4F516856",
  {
    "/api/v1/x402/tasks": {
      price: "$0.10",
      network: "base",
      config: {
        description: "Create a human task via HumanOracle",
      }
    },
    "/api/v1/x402/tasks/sync": {
      price: "$0.50",
      network: "base",
      config: {
        description: "Create a human task and wait for result (sync)",
      }
    }
  }
));

const supabase = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY);

// Load message moderation
let messageModeration;
try {
  messageModeration = require('./message-moderation');
} catch (e) {
  console.log('[Enterprise] message-moderation not found, moderation disabled');
  messageModeration = { moderateMessage: () => ({ allowed: true, violations: [] }) };
}

const CONFIG = {
  VAE_RATE: 100,
  WORKER_FEE: 0.20,
  VALID_CATEGORIES: [
    'writing', 'transcription', 'translation', 'data_entry', 'labeling',
    'research', 'survey', 'verification', 'moderation', 'micro',
    'audio', 'social', 'ai_training', 'other'
  ],
};

// ─── API Key Auth ─────────────────────────────────────────────────────────────

const API_KEYS = new Map();

async function loadAPIKeys() {
  try {
    const { data, error } = await supabase.from('api_keys').select('*').eq('active', true);
    if (error) {
      console.error('Failed to load API keys:', error);
      return;
    }
    if (data) {
      data.forEach(key => API_KEYS.set(key.key, key));
      console.log(`✅ Loaded ${data.length} API keys`);
    }
  } catch (e) {
    console.error('API key load error:', e);
  }
}

// Reload keys every 5 minutes (picks up new keys without restart)
setInterval(loadAPIKeys, 5 * 60 * 1000);

function authenticateAPI(req, res, next) {
  const apiKey = req.headers['x-api-key'] || (req.headers['authorization'] || '').replace('Bearer ', '');
  if (!apiKey || !API_KEYS.has(apiKey)) {
    return res.status(401).json({ error: 'Invalid API key. Send x-api-key or Authorization: Bearer header.' });
  }
  req.apiKey = API_KEYS.get(apiKey);
  req.userId = req.apiKey.user_id;
  next();
}

// ─── Rate Limiting ────────────────────────────────────────────────────────────

const rateLimitStore = new Map();
const RATE_LIMITS = {
  standard: { requests: 100, windowMs: 60000 },
  bulk: { requests: 10, windowMs: 60000 },
};

function rateLimitMiddleware(limitType = 'standard') {
  return (req, res, next) => {
    const apiKey = req.headers['x-api-key'] || 'anon';
    const key = apiKey + ':' + limitType;
    const now = Date.now();
    const limits = RATE_LIMITS[limitType] || RATE_LIMITS.standard;
    let data = rateLimitStore.get(key);
    if (!data || (now - data.windowStart) > limits.windowMs) {
      data = { count: 0, windowStart: now };
      rateLimitStore.set(key, data);
    }
    if (data.count >= limits.requests) {
      const resetIn = Math.ceil(Math.max(0, limits.windowMs - (now - data.windowStart)) / 1000);
      return res.status(429).json({ error: 'Rate limit exceeded', retryAfter: resetIn });
    }
    data.count++;
    res.set({
      'X-RateLimit-Limit': limits.requests,
      'X-RateLimit-Remaining': Math.max(0, limits.requests - data.count),
    });
    next();
  };
}

setInterval(() => {
  const now = Date.now();
  for (const [k, v] of rateLimitStore) {
    if (now - v.windowStart > 120000) rateLimitStore.delete(k);
  }
}, 300000);


// ═══════════════════════════════════════════════════════════════════════════════
// POST /api/v1/register — Self-service API key registration (PUBLIC, no auth)
// ═══════════════════════════════════════════════════════════════════════════════

const registrationLimiter = new Map(); // IP-based abuse prevention

app.post('/api/v1/register', async (req, res) => {
  try {
    const { company_name, email, webhook_url, use_case } = req.body;
    
    // Validate required fields
    if (!company_name || !email) {
      return res.status(400).json({
        error: 'company_name and email are required',
        example: {
          company_name: 'MoltoBot AI',
          email: 'dev@molto.bot',
          webhook_url: 'https://molto.bot/loopuman/callback',
          use_case: 'AI training data labeling'
        }
      });
    }
    
    // Validate email format
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(email)) {
      return res.status(400).json({ error: 'Invalid email format' });
    }
    
    // Rate limit: max 3 registrations per IP per hour
    const clientIP = req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.ip;
    const ipKey = `reg:${clientIP}`;
    const now = Date.now();
    let ipData = registrationLimiter.get(ipKey);
    if (!ipData || (now - ipData.start) > 3600000) {
      ipData = { count: 0, start: now };
      registrationLimiter.set(ipKey, ipData);
    }
    if (ipData.count >= 3) {
      return res.status(429).json({ error: 'Too many registrations. Try again in 1 hour.' });
    }
    
    // Check if email already registered
    const { data: existingUser } = await supabase
      .from('app_users')
      .select('id')
      .eq('email', email)
      .maybeSingle();
    
    if (existingUser) {
      // Check if they already have a key
      const { data: existingKey } = await supabase
        .from('api_keys')
        .select('key')
        .eq('user_id', existingUser.id)
        .eq('active', true)
        .maybeSingle();
      
      if (existingKey) {
        return res.status(409).json({
          error: 'Email already registered',
          message: 'An API key already exists for this email. Contact support@loopuman.com if you lost your key.'
        });
      }
      
      // User exists but no key — generate one
      const apiKey = 'lpm_' + crypto.randomBytes(32).toString('hex');
      await supabase.from('api_keys').insert({
        user_id: existingUser.id,
        key: apiKey,
        name: company_name,
        active: true,
      });
      
      ipData.count++;
      
      return res.status(201).json({
        message: 'API key created for existing account',
        api_key: apiKey,
        user_id: existingUser.id,
        important: '⚠️ Save this key now — it cannot be retrieved later.',
        next_steps: {
          deposit: 'Deposit funds via Telegram bot @loopuman_bot or send crypto to your deposit address',
          post_task: 'POST /api/v1/tasks with x-api-key header',
          docs: 'https://loopuman.com/developers',
        }
      });
    }
    
    // Create new user account (API-only, no Telegram)
    const apiUserId = `api_${crypto.randomBytes(8).toString('hex')}`;
    const referralCode = crypto.randomBytes(4).toString('hex').toUpperCase();
    
    const { data: newUser, error: userError } = await supabase
      .from('app_users')
      .insert({
        telegram_id: apiUserId,
        first_name: company_name,
        email: email,
        country: 'API',
        referral_code: referralCode,
        role: 'requester',
      })
      .select()
      .single();
    
    if (userError || !newUser) {
      console.error('[Register] User creation error:', userError);
      return res.status(500).json({ error: 'Registration failed. Try again.' });
    }
    
    // Promo code credits
    const PROMO_CODES = {
      'CLAW500': { credits: 500, limit: 10, description: 'OpenClaw Founding Tester' },
      'LOBSTER': { credits: 200, limit: 50, description: 'Early Access' },
    };
    const DEFAULT_CREDITS = 50;
    let bonusCredits = DEFAULT_CREDITS;
    let promoApplied = null;
    const promo_code = (req.body.promo_code || '').toUpperCase().trim();
    if (promo_code && PROMO_CODES[promo_code]) {
      const { count } = await supabase.from('app_users').select('*', { count: 'exact', head: true }).like('bio', `%promo:${promo_code}%`);
      if (count < PROMO_CODES[promo_code].limit) {
        bonusCredits = PROMO_CODES[promo_code].credits;
        promoApplied = promo_code;
      }
    }
    // Create VAE balance
    await supabase.from('vae_balances').insert({
      user_id: newUser.id,
      available: bonusCredits,
      pending: 0,
      total_earned: bonusCredits,
      total_withdrawn: 0,
    });
    
    // Generate API key
    const apiKey = 'lpm_' + crypto.randomBytes(32).toString('hex');
    
    const { error: keyError } = await supabase.from('api_keys').insert({
      user_id: newUser.id,
      key: apiKey,
      name: company_name,
      active: true,
    });
    
    if (keyError) {
      console.error('[Register] API key creation error:', keyError);
      return res.status(500).json({ error: 'Key generation failed. Try again.' });
    }
    
    // Store use case + promo tracking
    const bioParts = [];
    if (use_case) bioParts.push('API: ' + use_case);
    if (promoApplied) bioParts.push('promo:' + promoApplied);
    if (bioParts.length > 0) {
      await supabase.from('app_users').update({
        bio: bioParts.join(' | '),
      }).eq('id', newUser.id);
    }
    
    // Reload API keys in memory
    API_KEYS.set(apiKey, { key: apiKey, user_id: newUser.id, name: company_name, active: true });
    
    ipData.count++;
    
    console.log(`[Register] New API account: ${company_name} (${email})`);
    
    res.status(201).json({
      message: 'Welcome to Loopuman — The Human Layer for AI',
      api_key: apiKey,
      user_id: newUser.id,
      important: '⚠️ Save this API key now — it cannot be retrieved later.',
      balance: {
        available_vae: bonusCredits,
        available_usd: (bonusCredits / 100).toFixed(2),
        note: bonusCredits > 0 ? 'Free credits applied!' + (promoApplied ? ' Promo: ' + promoApplied : ' Welcome bonus.') : 'Deposit funds to start posting tasks',
      },
      next_steps: {
        '1_deposit': 'POST /api/v1/deposit with method "stripe" or "crypto" to fund your account',
        '2_post_task': 'POST /api/v1/tasks with x-api-key header',
        '3_get_results': 'GET /api/v1/tasks/:id to poll, or set webhook_url for push notifications',
        docs: 'https://loopuman.com/developers',
      },
      endpoints: {
        'POST /api/v1/tasks': 'Create single task ($0.10 - $1,000)',
        'POST /api/v1/tasks/sync': 'Create task & wait for human result',
        'POST /api/v1/tasks/bulk': 'Bulk upload up to 10,000 tasks',
        'GET /api/v1/tasks/:id': 'Poll task status + submissions',
        'POST /api/v1/submissions/:id/approve': 'Approve submission',
        'GET /api/v1/balance': 'Check balance',
      },
      pricing: {
        commission: '20% added to task budget',
        example: '$1.00 task costs you $1.20, worker receives $0.80',
        min_task: '$0.10',
        payments: 'Workers paid instantly in crypto (USDC/USDT/cUSD)',
      }
    });
    
  } catch (error) {
    console.error('[Register] Error:', error);
    res.status(500).json({ error: 'Registration failed' });
  }
});

// Clean up registration limiter
setInterval(() => {
  const now = Date.now();
  for (const [k, v] of registrationLimiter) {
    if (now - v.start > 3600000) registrationLimiter.delete(k);
  }
}, 600000);


// ═══════════════════════════════════════════════════════════════════════════════
// POST /api/v1/deposit — Fund account via Stripe or Crypto (AUTHENTICATED)
// ═══════════════════════════════════════════════════════════════════════════════

let stripe;
try {
  stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
} catch (e) {
  console.log('[Enterprise] Stripe not available:', e.message);
}

// Preset deposit amounts
const DEPOSIT_AMOUNTS = [
  { vae: 500,   usd: 5.00,   label: 'Ⓥ500 ($5)' },
  { vae: 1000,  usd: 10.00,  label: 'Ⓥ1,000 ($10)' },
  { vae: 2500,  usd: 25.00,  label: 'Ⓥ2,500 ($25)' },
  { vae: 5000,  usd: 50.00,  label: 'Ⓥ5,000 ($50)' },
  { vae: 10000, usd: 100.00, label: 'Ⓥ10,000 ($100)' },
  { vae: 50000, usd: 500.00, label: 'Ⓥ50,000 ($500)' },
];

// Crypto deposit info
const TREASURY_ADDRESS = process.env.TREASURY_ADDRESS || '0x9F52C166c90567B7F9906c6319ef90aF4F516856';
const SUPPORTED_TOKENS = ['USDC', 'USDT', 'cUSD'];

app.post('/api/v1/deposit', async (req, res) => {
  try {
    const apiKeyHeader = req.headers['x-api-key'];
    if (!apiKeyHeader) return res.status(401).json({ error: 'x-api-key header required' });
    
    const keyData = API_KEYS.get(apiKeyHeader);
    if (!keyData || !keyData.active) return res.status(401).json({ error: 'Invalid API key' });
    
    const { method, amount_usd, success_url, cancel_url } = req.body;
    
    if (!method) {
      return res.status(400).json({
        error: 'Specify deposit method',
        methods: {
          stripe: {
            description: 'Pay with credit/debit card',
            params: { method: 'stripe', amount_usd: 10.00, success_url: 'https://yourapp.com/success', cancel_url: 'https://yourapp.com/cancel' },
            preset_amounts: DEPOSIT_AMOUNTS,
          },
          crypto: {
            description: 'Send USDC, USDT, or cUSD on Celo network',
            params: { method: 'crypto' },
            note: 'Balance credited automatically within 2 minutes',
          }
        }
      });
    }
    
    // ─── CRYPTO DEPOSIT ──────────────────────────────────────────────
    if (method === 'crypto') {
      return res.json({
        method: 'crypto',
        network: 'Celo (Chain ID: 42220)',
        deposit_address: TREASURY_ADDRESS,
        supported_tokens: SUPPORTED_TOKENS,
        instructions: [
          `Send USDC, USDT, or cUSD to: ${TREASURY_ADDRESS}`,
          'Use the Celo network (not Ethereum mainnet)',
          'Minimum deposit: $1.00',
          'Balance credited automatically within 2 minutes',
          'Your deposit is matched to your account via the Celo blockchain',
        ],
        important: '⚠️ Only send on Celo network. Tokens sent on other chains will be lost.',
        exchange_rate: '100 VAE = $1.00 USD',
        check_balance: 'GET /api/v1/balance',
      });
    }
    
    // ─── STRIPE DEPOSIT ──────────────────────────────────────────────
    if (method === 'stripe') {
      if (!stripe) {
        return res.status(503).json({ error: 'Stripe payments temporarily unavailable' });
      }
      
      // Validate amount
      const amt = parseFloat(amount_usd);
      if (!amt || amt < 1 || amt > 10000) {
        return res.status(400).json({
          error: 'amount_usd must be between $1.00 and $10,000.00',
          preset_amounts: DEPOSIT_AMOUNTS,
        });
      }
      
      const vaeAmount = Math.round(amt * 100); // $1 = 100 VAE
      const amountCents = Math.round(amt * 100);
      
      // Create Stripe Checkout Session
      const sessionParams = {
        payment_method_types: ['card'],
        mode: 'payment',
        line_items: [{
          price_data: {
            currency: 'usd',
            product_data: {
              name: `Loopuman Deposit — Ⓥ${vaeAmount.toLocaleString()}`,
              description: `${vaeAmount} VAE credits for posting tasks on Loopuman`,
            },
            unit_amount: amountCents,
          },
          quantity: 1,
        }],
        metadata: {
          user_id: keyData.user_id,
          amount_vae: vaeAmount.toString(),
          source: 'enterprise_api',
        },
        success_url: success_url || 'https://loopuman.com/deposit-success?session_id={CHECKOUT_SESSION_ID}',
        cancel_url: cancel_url || 'https://loopuman.com/deposit-cancel',
      };
      
      const session = await stripe.checkout.sessions.create(sessionParams);
      
      console.log(`[Deposit] Stripe session created: ${session.id} for user ${keyData.user_id}, $${amt} = Ⓥ${vaeAmount}`);
      
      return res.json({
        method: 'stripe',
        checkout_url: session.url,
        session_id: session.id,
        amount_usd: amt,
        vae_amount: vaeAmount,
        expires_in: '30 minutes',
        instructions: [
          'Open the checkout_url to complete payment',
          'Balance is credited automatically after payment',
          'Use GET /api/v1/balance to verify',
        ],
      });
    }
    
    return res.status(400).json({ error: 'Invalid method. Use "stripe" or "crypto".' });
    
  } catch (error) {
    console.error('[Deposit] Error:', error);
    res.status(500).json({ error: 'Deposit failed. Try again.' });
  }
});

// GET /api/v1/deposit/crypto — Quick crypto deposit info (no auth needed)
app.get('/api/v1/deposit/crypto', (req, res) => {
  res.json({
    network: 'Celo (Chain ID: 42220)',
    deposit_address: TREASURY_ADDRESS,
    supported_tokens: SUPPORTED_TOKENS,
    exchange_rate: '100 VAE = $1.00 USD',
    minimum_deposit: '$1.00',
    auto_credit: 'Within 2 minutes',
  });
});


// ═══════════════════════════════════════════════════════════════════════════════
// HELPER: Atomic balance deduction
// ═══════════════════════════════════════════════════════════════════════════════

async function deductBalanceAtomic(userId, amount) {
  const { data, error } = await supabase.rpc('atomic_balance_op', {
    p_user_id: userId,
    p_operation: 'deduct_available',
    p_amount: amount,
  });
  if (error) {
    console.error('[Enterprise] Atomic deduct error:', error.message);
    return false;
  }
  return data?.success === true;
}

async function refundBalanceAtomic(userId, amount) {
  const { data, error } = await supabase.rpc('atomic_balance_op', {
    p_user_id: userId,
    p_operation: 'add_available_no_earned',
    p_amount: amount,
  });
  if (error) console.error('[Enterprise] Atomic refund error:', error.message);
  return !error;
}


// ═══════════════════════════════════════════════════════════════════════════════
// HELPER: Content moderation for API tasks
// ═══════════════════════════════════════════════════════════════════════════════

function moderateTaskContent(title, description) {
  const titleCheck = messageModeration.moderateMessage(title);
  const descCheck = messageModeration.moderateMessage(description);
  
  if (!titleCheck.allowed || !descCheck.allowed) {
    const violations = [...(titleCheck.violations || []), ...(descCheck.violations || [])];
    const unique = violations.filter((v, i, arr) => arr.findIndex(x => x.category === v.category) === i);
    return { blocked: true, violations: unique };
  }
  return { blocked: false, violations: [] };
}


// ═══════════════════════════════════════════════════════════════════════════════
// HELPER: Webhook delivery (with retry)
// ═══════════════════════════════════════════════════════════════════════════════

async function deliverWebhook(webhookUrl, payload) {
  if (!webhookUrl) return false;
  
  for (let attempt = 1; attempt <= 3; attempt++) {
    try {
      const response = await fetch(webhookUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(payload),
        signal: AbortSignal.timeout(10000),
      });
      if (response.ok) {
        console.log(`[Webhook] ✅ Delivered to ${webhookUrl} (attempt ${attempt})`);
        return true;
      }
      console.error(`[Webhook] ❌ ${response.status} from ${webhookUrl} (attempt ${attempt})`);
    } catch (err) {
      console.error(`[Webhook] ❌ Failed (attempt ${attempt}):`, err.message);
    }
    if (attempt < 3) await new Promise(r => setTimeout(r, attempt * 2000));
  }
  return false;
}

/**
 * Trigger webhook for any task (batch OR single).
 * Called after submission approval.
 */
async function triggerTaskWebhook(taskId, submissionId) {
  try {
    const { data: task } = await supabase
      .from('tasks')
      .select('id, title, status, max_workers, callback_url, metadata, batch_id')
      .eq('id', taskId)
      .single();
    
    if (!task) return;
    
    // Determine webhook URL: task callback > batch webhook
    let webhookUrl = task.callback_url;
    if (!webhookUrl && task.batch_id) {
      const { data: batch } = await supabase
        .from('task_batches')
        .select('webhook_url')
        .eq('id', task.batch_id)
        .single();
      webhookUrl = batch?.webhook_url;
    }
    if (!webhookUrl) return;
    
    // Get the approved submission
    const { data: sub } = await supabase
      .from('submissions')
      .select('id, content, status, rating, submitted_at, approved_at, worker_id')
      .eq('id', submissionId)
      .single();
    
    if (!sub || sub.status !== 'approved') return;
    
    // Get all approved submissions for progress count
    const { count: approvedCount } = await supabase
      .from('submissions')
      .select('*', { count: 'exact', head: true })
      .eq('task_id', taskId)
      .eq('status', 'approved');
    
    const payload = {
      event: 'task.submission_approved',
      task_id: taskId,
      task_title: task.title,
      submission: {
        id: sub.id,
        content: sub.content,
        rating: sub.rating,
        submitted_at: sub.submitted_at,
        approved_at: sub.approved_at,
      },
      progress: {
        approved: approvedCount || 0,
        total_slots: task.max_workers,
      },
      metadata: task.metadata || {},
      timestamp: new Date().toISOString(),
    };
    
    await deliverWebhook(webhookUrl, payload);
    
    // Update batch progress if applicable
    if (task.batch_id) {
      const { data: batchTasks } = await supabase
        .from('tasks')
        .select('status')
        .eq('batch_id', task.batch_id);
      const completed = (batchTasks || []).filter(t => t.status === 'completed').length;
      const total = batchTasks?.length || 0;
      await supabase.from('task_batches').update({
        completed_tasks: completed,
        status: completed >= total ? 'completed' : 'active',
        updated_at: new Date().toISOString(),
      }).eq('id', task.batch_id);
    }
  } catch (e) {
    console.error('[Webhook] triggerTaskWebhook error:', e.message);
  }
}


// ═══════════════════════════════════════════════════════════════════════════════
// HELPER: Notify workers about new task
// ═══════════════════════════════════════════════════════════════════════════════

async function notifyWorkersAboutTask(task) {
  try {
    const { Telegraf } = require('telegraf');
    const tgBot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN);
    
    const { data: workers } = await supabase
      .from('app_users')
      .select('id, telegram_id')
      .gt('trust_score', 0)
      .not('telegram_id', 'is', null)
      .limit(30);
    
    if (!workers?.length) return;
    
    const earningsVae = Math.floor(task.budget * 0.8);
    const msg = `🔔 <b>New Task Available!</b>\n\n📋 "${task.title}"\n💰 Earn: Ⓥ${earningsVae} ($${(earningsVae / 100).toFixed(2)})\n📁 ${task.category}\n\nTap Find Work to accept!`;
    
    let sent = 0;
    for (const w of workers) {
      if (sent >= 20) break;
      try {
        await tgBot.telegram.sendMessage(w.telegram_id, msg, { parse_mode: 'HTML' });
        sent++;
      } catch (e) { /* silent */ }
    }
    console.log(`[Enterprise] Notified ${sent} workers about task "${task.title}"`);
  } catch (e) {
    console.error('[Enterprise] Worker notification error:', e.message);
  }
}


// ═══════════════════════════════════════════════════════════════════════════════
// POST /api/v1/tasks — Single async task (AI agent or enterprise)
// ═══════════════════════════════════════════════════════════════════════════════

app.post('/api/v1/tasks', authenticateAPI, rateLimitMiddleware('standard'), async (req, res) => {
  try {
    const {
      title, description = '', category = 'other',
      budget_vae, budget_usd, max_workers = 1,
      duration_hours = 24, webhook_url, priority, metadata,
      auto_approve = false
    } = req.body;
    
    if (!title) return res.status(400).json({ error: 'title is required' });
    if (!description && !title) return res.status(400).json({ error: 'description is required' });
    
    // Category validation
    const cat = category || 'other';
    if (!CONFIG.VALID_CATEGORIES.includes(cat)) {
      return res.status(400).json({ error: `Invalid category. Valid: ${CONFIG.VALID_CATEGORIES.join(', ')}` });
    }

    // Location validation for local tasks
    const location = req.body.location || null;
    const locationKeywords = /(go to|visit|walk to|drive to|check if|verify.*open|take.*photo|pick up|deliver|drop off|nearby|street|address|store|shop|restaurant|office|building)/i;
    const hasLocationInText = locationKeywords.test(description || '') || locationKeywords.test(title || '');
    
    if (cat === 'local' && !location) {
      return res.status(400).json({
        error: 'Location required for local tasks',
        message: 'Tasks with category "local" require a location field. Provide an address, city, or coordinates.',
        example: { location: '123 Main St, Nairobi, Kenya' },
        hint: 'If this is a remote task, use a different category like "research" or "other".',
      });
    }
    
    // AI moderation: detect likely local tasks miscategorized as non-local
    if (cat !== 'local' && hasLocationInText && !location) {
      // Don't block — just add a warning in the response metadata
      if (!req.body.metadata) req.body.metadata = {};
      req.body.metadata._location_warning = 'This task description suggests physical/local work but no location was provided. Consider using category "local" with a location field for better worker matching.';
    }
    
    // Budget: accept VAE or USD
    let budget;
    if (budget_vae) {
      budget = parseInt(budget_vae);
    } else if (budget_usd) {
      budget = Math.round(parseFloat(budget_usd) * CONFIG.VAE_RATE);
    } else {
      budget = 100; // Default Ⓥ100 ($1)
    }
    
    if (budget < 25 || budget > 100000) {
      return res.status(400).json({ error: 'Budget must be between Ⓥ10 ($0.10) and Ⓥ100,000 ($1,000)' });
    }

    // Estimated time + minimum hourly rate enforcement
    
    // Category-based minimum budgets — complex tasks can't be dirt cheap
    const CATEGORY_MINIMUMS = {
      writing: 100,       // $1.00 — writing takes real time
      translation: 100,   // $1.00 — language work is skilled
      research: 75,       // $0.75 — research needs thought
      local: 50,          // $0.50 — physical tasks need fair pay
      image: 25,          // $0.25 — quick visual tasks
      audio: 50,          // $0.50 — audio work is skilled
      data: 25,           // $0.25 — data entry microtasks
      survey: 25,         // $0.25 — quick responses OK
      other: 25,          // $0.25 — default
    };
    const catMin = CATEGORY_MINIMUMS[cat] || 25;
    if (budget < catMin) {
      return res.status(400).json({
        error: 'Budget too low for this category',
        category: cat,
        minimum_vae: catMin,
        minimum_usd: (catMin / CONFIG.VAE_RATE).toFixed(2),
        your_budget_vae: budget,
        message: `${cat} tasks require at least ${catMin} VAE (${(catMin / CONFIG.VAE_RATE).toFixed(2)}). Use a simpler category for cheaper tasks.`,
      });
    }

    // REQUIRED: estimated_seconds tells us expected task duration
    const estimatedSeconds = parseInt(req.body.estimated_seconds) || null;
    if (!estimatedSeconds || estimatedSeconds < 1) {
      return res.status(400).json({
        error: 'estimated_seconds is required',
        message: 'Provide the expected time (in seconds) for a worker to complete this task. Loopuman enforces a $6/hr minimum effective rate.',
        examples: {
          '5_second_microtask': { estimated_seconds: 5, min_budget_vae: 1 },
          '30_second_task': { estimated_seconds: 30, min_budget_vae: 5 },
          '2_minute_task': { estimated_seconds: 120, min_budget_vae: 20 },
          '10_minute_task': { estimated_seconds: 600, min_budget_vae: 100 },
        }
      });
    }
    if (estimatedSeconds) {
      const hourlyRate = (budget / CONFIG.VAE_RATE) / (estimatedSeconds / 3600);
      if (hourlyRate < 6) {
        const minBudget = Math.ceil(6 * (estimatedSeconds / 3600) * CONFIG.VAE_RATE);
        return res.status(400).json({
          error: 'Task pay rate too low',
          effective_hourly: '$' + hourlyRate.toFixed(2) + '/hr',
          minimum_hourly: '$6.00/hr',
          suggested_budget_vae: minBudget,
          suggested_budget_usd: '$' + (minBudget / CONFIG.VAE_RATE).toFixed(2),
          message: 'Loopuman enforces a $6/hr minimum effective rate to ensure fair worker pay.'
        });
      }
    }
    
    const workers = Math.min(Math.max(parseInt(max_workers) || 1, 1), 100);
    const totalBudget = budget * workers;
    const durationDays = Math.max(Math.ceil((parseInt(duration_hours) || 24) / 24), 1);
    
    // Content moderation
    const modResult = moderateTaskContent(title, description);
    if (modResult.blocked) {
      return res.status(400).json({
        error: 'Task blocked by content moderation',
        violations: modResult.violations.map(v => v.message),
      });
    }
    
    // ATOMIC: Deduct balance
    const requesterCost = Math.ceil(totalBudget * 1.2);
    const deducted = await deductBalanceAtomic(req.userId, requesterCost);
    if (!deducted) {
      // Check actual balance for error message
      const { data: bal } = await supabase.from('vae_balances').select('available').eq('user_id', req.userId).single();
      return res.status(402).json({
        error: 'Insufficient balance',
        required: totalBudget,
        required_usd: (totalBudget / CONFIG.VAE_RATE).toFixed(2),
        available: bal?.available || 0,
        available_usd: ((bal?.available || 0) / CONFIG.VAE_RATE).toFixed(2),
      });
    }
    
    // Create task
    const { data: task, error: taskError } = await supabase
      .from('tasks')
      .insert({
        requester_id: req.userId,
        title,
        description: description || title,
        category: cat,
        budget: totalBudget,
        max_workers: workers,
        duration_days: durationDays,
        status: 'active',
        source: 'api',
        callback_url: webhook_url || null,
        reference_media: (req.body.reference_media || []).map(m => {
          if (typeof m === 'string') {
            const isImage = /\.(jpg|jpeg|png|gif|webp|bmp)$/i.test(m);
            return { type: isImage ? 'image' : 'document', url: m, filename: m.split('/').pop() || 'file' };
          }
          return m;
        }),
        expires_at: new Date(Date.now() + durationDays * 86400000).toISOString(),
        is_local: cat === 'local',
        location: location || null,
        metadata: { ...(metadata || {}), api_source: true, priority: priority || 'normal', auto_approve: !!auto_approve, estimated_seconds: estimatedSeconds || null, location: location || null },
      })
      .select()
      .single();
    
    if (taskError || !task) {
      console.error('[Enterprise] Task creation error:', taskError);
      // Refund on failure
      await refundBalanceAtomic(req.userId, totalBudget);
      return res.status(500).json({ error: 'Failed to create task' });
    }
    
    // Record transaction
    await supabase.from('transactions').insert({
      user_id: req.userId,
      type: 'task_posted',
      amount: -totalBudget,
      description: `API task: ${title}`,
      task_id: task.id,
    });
    
    // Notify workers if high priority
    if (priority === 'high') {
      notifyWorkersAboutTask(task).catch(() => {});
    }
    
    console.log(`[Enterprise] Task created: "${title}" — Ⓥ${totalBudget} ($${(totalBudget / CONFIG.VAE_RATE).toFixed(2)})`);
    
    res.status(201).json({
      task_id: task.id,
      status: 'active',
      budget_vae: totalBudget,
      budget_usd: (totalBudget / CONFIG.VAE_RATE).toFixed(2),
      max_workers: workers,
      expires_at: task.expires_at,
      webhook_url: webhook_url || null,
      poll_url: `/api/v1/tasks/${task.id}`,
    });
  } catch (e) {
    console.error('[Enterprise] POST /api/v1/tasks error:', e);
    res.status(500).json({ error: 'Internal server error' });
  }
});


// ═══════════════════════════════════════════════════════════════════════════════
// POST /api/v1/tasks/sync — Synchronous: block until human responds
// ═══════════════════════════════════════════════════════════════════════════════

app.post('/api/v1/tasks/sync', authenticateAPI, rateLimitMiddleware('standard'), async (req, res) => {
  const {
    title, description = '', category = 'ai_training',
    budget_vae, budget_usd, timeout_seconds = 300,
    auto_approve = true, webhook_url
  } = req.body;
  
  if (!title) return res.status(400).json({ error: 'title is required' });
  
  let budget;
  if (budget_vae) budget = parseInt(budget_vae);
  else if (budget_usd) budget = Math.round(parseFloat(budget_usd) * CONFIG.VAE_RATE);
  else budget = 50;
  
  // Content moderation
  const modResult = moderateTaskContent(title, description || title);
  if (modResult.blocked) {
    return res.status(400).json({ error: 'Task blocked by content moderation' });
  }
  
  // ATOMIC: Deduct balance BEFORE creating task
  const requesterCost = Math.ceil(budget * 1.2);
  const deducted = await deductBalanceAtomic(req.userId, requesterCost);
  if (!deducted) {
    return res.status(402).json({ error: 'Insufficient balance' });
  }
  
  const startTime = Date.now();
  
  try {
    const { data: task, error: taskError } = await supabase
      .from('tasks')
      .insert({
        title,
        description: description || title,
        category,
        budget,
        max_workers: 1,
        status: 'active',
        requester_id: req.userId,
        source: 'api_sync',
        callback_url: webhook_url || null,
        expires_at: new Date(Date.now() + Math.max(timeout_seconds, 3600) * 1000).toISOString(),
      })
      .select()
      .single();
    
    if (taskError || !task) {
      await refundBalanceAtomic(req.userId, budget);
      return res.status(500).json({ error: 'Failed to create task' });
    }
    
    // Record transaction
    await supabase.from('transactions').insert({
      user_id: req.userId, type: 'task_posted', amount: -budget,
      description: `Sync API task: ${title}`, task_id: task.id,
    });
    
    // Notify workers immediately (sync tasks are urgent)
    notifyWorkersAboutTask(task).catch(() => {});
    
    console.log(`[Sync] Created task ${task.id.slice(0,8)}, waiting up to ${timeout_seconds}s...`);
    
    // Poll for completion
    const pollInterval = 2000;
    const maxWait = Math.min(timeout_seconds * 1000, 600000); // Cap at 10 minutes
    
    while (Date.now() - startTime < maxWait) {
      const { data: submissions } = await supabase
        .from('submissions')
        .select('id, content, worker_id, status, created_at')
        .eq('task_id', task.id)
        .in('status', ['approved', 'submitted'])
        .order('created_at', { ascending: false })
        .limit(1);
      
      if (submissions?.length > 0) {
        const sub = submissions[0];
        
        if (auto_approve && sub.status === 'submitted') {
          await supabase.rpc('approve_submission_safe', { p_submission_id: sub.id }).catch(() => {});
        }
        
        return res.json({
          status: 'completed',
          task_id: task.id,
          submission_id: sub.id,
          response: sub.content,
          worker_id: sub.worker_id,
          completed_in_seconds: Math.round((Date.now() - startTime) / 1000),
        });
      }
      
      await new Promise(resolve => setTimeout(resolve, pollInterval));
    }
    
    // Timeout
    return res.json({
      status: 'timeout',
      task_id: task.id,
      message: 'No worker completed within timeout. Poll /api/v1/tasks/:id for updates.',
      waited_seconds: Math.round((Date.now() - startTime) / 1000),
    });
  } catch (error) {
    console.error('[Sync] Error:', error);
    return res.status(500).json({ error: 'Internal server error' });
  }
});


// ═══════════════════════════════════════════════════════════════════════════════
// POST /api/v1/tasks/bulk — Bulk upload (enterprise batch)
// ═══════════════════════════════════════════════════════════════════════════════

app.post('/api/v1/tasks/bulk', authenticateAPI, rateLimitMiddleware('bulk'), async (req, res) => {
  try {
    const { tasks, webhook_url } = req.body;
    
    if (!Array.isArray(tasks) || tasks.length === 0) {
      return res.status(400).json({ error: 'tasks array is required' });
    }
    if (tasks.length > 10000) {
      return res.status(400).json({ error: 'Maximum 10,000 tasks per batch' });
    }
    
    // Calculate total cost
    let totalCost = 0;
    const taskRecords = [];
    const batchId = crypto.randomUUID();
    
    for (const t of tasks) {
      let budget;
      if (t.budget_vae) budget = parseInt(t.budget_vae);
      else if (t.budget_usd) budget = Math.round(parseFloat(t.budget_usd) * CONFIG.VAE_RATE);
      else budget = 100;
      
      const workers = parseInt(t.max_workers) || 1;
      totalCost += Math.ceil(budget * 1.2) * workers; // budget + 20% commission
      
      taskRecords.push({
        requester_id: req.userId,
        title: t.title || 'Untitled Task',
        description: t.description || t.title || 'Complete this task',
        category: t.category || 'other',
        budget: budget * workers,
        max_workers: workers,
        expires_at: t.deadline || new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString(),
        status: 'active',
        source: 'api_batch',
        batch_id: batchId,
        callback_url: webhook_url || null,
        metadata: { batch_id: batchId, api_source: true },
      });
    }
    
    // ATOMIC: Deduct total balance
    const requesterCost = totalCost; // commission already included in loop above
    const deducted = await deductBalanceAtomic(req.userId, requesterCost);
    if (!deducted) {
      const { data: bal } = await supabase.from('vae_balances').select('available').eq('user_id', req.userId).single();
      return res.status(402).json({
        error: 'Insufficient balance',
        required: totalCost,
        required_usd: (totalCost / CONFIG.VAE_RATE).toFixed(2),
        available: bal?.available || 0,
      });
    }
    
    // Create batch record
    const { error: batchError } = await supabase.from('task_batches').insert({
      id: batchId,
      user_id: req.userId,
      total_tasks: tasks.length,
      webhook_url: webhook_url || null,
      status: 'processing',
    });
    
    if (batchError) {
      await refundBalanceAtomic(req.userId, totalCost);
      return res.status(500).json({ error: 'Failed to create batch' });
    }
    
    // Insert tasks in chunks
    let created = 0;
    for (let i = 0; i < taskRecords.length; i += 500) {
      const chunk = taskRecords.slice(i, i + 500);
      const { data, error } = await supabase.from('tasks').insert(chunk).select('id');
      if (error) {
        console.error(`[Enterprise] Batch insert error at chunk ${i}:`, error.message);
      } else {
        created += (data?.length || 0);
      }
    }
    
    if (created === 0) {
      // Full failure — refund
      await refundBalanceAtomic(req.userId, totalCost);
      await supabase.from('task_batches').update({ status: 'failed' }).eq('id', batchId);
      return res.status(500).json({ error: 'Failed to create tasks' });
    }
    
    // Partial failure — refund for uncreated tasks
    if (created < tasks.length) {
      const refundAmount = Math.floor(totalCost * ((tasks.length - created) / tasks.length));
      if (refundAmount > 0) await refundBalanceAtomic(req.userId, refundAmount);
    }
    
    // Record transaction
    await supabase.from('transactions').insert({
      user_id: req.userId, type: 'task_posted', amount: -totalCost,
      description: `Bulk batch: ${created} tasks`, task_id: null,
    });
    
    // Update batch status
    await supabase.from('task_batches').update({
      status: 'active',
      task_ids: null, // task_ids column may exist from old schema
    }).eq('id', batchId);
    
    console.log(`[Enterprise] Batch created: ${created}/${tasks.length} tasks, Ⓥ${totalCost}`);
    
    res.status(201).json({
      batch_id: batchId,
      tasks_created: created,
      tasks_requested: tasks.length,
      total_cost_vae: totalCost,
      total_cost_usd: (totalCost / CONFIG.VAE_RATE).toFixed(2),
      webhook_url: webhook_url || null,
      status_url: `/api/v1/batches/${batchId}`,
    });
  } catch (error) {
    console.error('[Enterprise] Bulk error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});


// ═══════════════════════════════════════════════════════════════════════════════
// GET /api/v1/tasks/:id — Get task status + submissions
// ═══════════════════════════════════════════════════════════════════════════════

app.get('/api/v1/tasks/:id', authenticateAPI, rateLimitMiddleware('standard'), async (req, res) => {
  try {
    const { data: task, error } = await supabase
      .from('tasks')
      .select(`
        id, title, description, category, budget, max_workers, status,
        created_at, expires_at, callback_url, metadata,
        submissions(id, status, content, worker_payment, created_at, submitted_at, approved_at, rating)
      `)
      .eq('id', req.params.id)
      .eq('requester_id', req.userId)
      .single();
    
    if (error || !task) return res.status(404).json({ error: 'Task not found' });
    
    const approved = (task.submissions || []).filter(s => s.status === 'approved');
    const pending = (task.submissions || []).filter(s => s.status === 'submitted');
    const inProgress = (task.submissions || []).filter(s => s.status === 'in_progress');
    
    res.json({
      task_id: task.id,
      title: task.title,
      description: task.description,
      category: task.category,
      status: task.status,
      budget_vae: task.budget,
      budget_usd: (task.budget / CONFIG.VAE_RATE).toFixed(2),
      max_workers: task.max_workers,
      created_at: task.created_at,
      expires_at: task.expires_at,
      progress: {
        approved: approved.length,
        pending_review: pending.length,
        in_progress: inProgress.length,
        total_slots: task.max_workers,
      },
      submissions: approved.map(s => ({
        submission_id: s.id,
        content: s.content,
        submitted_at: s.submitted_at,
        approved_at: s.approved_at,
        rating: s.rating,
      })),
      pending_submissions: pending.map(s => ({
        submission_id: s.id,
        content: s.content,
        submitted_at: s.submitted_at,
      })),
      metadata: task.metadata,
    });
  } catch (e) {
    console.error('[Enterprise] GET /api/v1/tasks/:id error:', e);
    res.status(500).json({ error: 'Internal server error' });
  }
});


// ═══════════════════════════════════════════════════════════════════════════════
// GET /api/v1/tasks — List all tasks for this API key
// ═══════════════════════════════════════════════════════════════════════════════

app.get('/api/v1/tasks', authenticateAPI, rateLimitMiddleware('standard'), async (req, res) => {
  try {
    const status = req.query.status || null;
    const limit = Math.min(parseInt(req.query.limit) || 50, 200);
    const offset = parseInt(req.query.offset) || 0;
    
    let query = supabase
      .from('tasks')
      .select('id, title, category, status, budget, max_workers, created_at, expires_at', { count: 'exact' })
      .eq('requester_id', req.userId)
      .order('created_at', { ascending: false })
      .range(offset, offset + limit - 1);
    
    if (status) query = query.eq('status', status);
    
    const { data: tasks, count, error } = await query;
    if (error) return res.status(500).json({ error: 'Query failed' });
    
    res.json({
      tasks: (tasks || []).map(t => ({
        task_id: t.id,
        title: t.title,
        category: t.category,
        status: t.status,
        budget_vae: t.budget,
        budget_usd: (t.budget / CONFIG.VAE_RATE).toFixed(2),
        created_at: t.created_at,
      })),
      total: count || 0,
      limit,
      offset,
    });
  } catch (e) {
    res.status(500).json({ error: 'Internal server error' });
  }
});


// ═══════════════════════════════════════════════════════════════════════════════
// POST /api/v1/submissions/:id/approve — Approve submission via API
// ═══════════════════════════════════════════════════════════════════════════════

app.post('/api/v1/submissions/:id/approve', authenticateAPI, rateLimitMiddleware('standard'), async (req, res) => {
  try {
    const { rating } = req.body;
    
    // Verify submission belongs to this user's task
    const { data: sub, error } = await supabase
      .from('submissions')
      .select('id, status, task_id, worker_id, worker_payment, task:tasks(requester_id, title, budget)')
      .eq('id', req.params.id)
      .single();
    
    if (error || !sub) return res.status(404).json({ error: 'Submission not found' });
    if (sub.task?.requester_id !== req.userId) return res.status(403).json({ error: 'Not your task' });
    if (sub.status === 'approved') return res.status(409).json({ error: 'Already approved' });
    if (sub.status !== 'submitted') return res.status(409).json({ error: `Cannot approve — status is ${sub.status}` });
    
    // Use atomic approval RPC
    const { data: rpcResult, error: rpcError } = await supabase.rpc('approve_submission_safe', {
      p_submission_id: sub.id,
    });
    
    if (rpcError || !rpcResult?.success) {
      return res.status(500).json({ error: rpcResult?.error || 'Approval failed' });
    }
    
    // Set rating if provided
    if (rating && rating >= 1 && rating <= 5) {
      await supabase.from('submissions').update({ rating }).eq('id', sub.id);
    }
    
    // Trigger webhook
    triggerTaskWebhook(sub.task_id, sub.id).catch(() => {});
    
    res.json({
      submission_id: sub.id,
      status: 'approved',
      payment: rpcResult.payment,
      task_id: sub.task_id,
    });
  } catch (e) {
    console.error('[Enterprise] Approve error:', e);
    res.status(500).json({ error: 'Internal server error' });
  }
});


// ═══════════════════════════════════════════════════════════════════════════════
// POST /api/v1/submissions/:id/reject — Reject submission via API
// ═══════════════════════════════════════════════════════════════════════════════

app.post('/api/v1/submissions/:id/reject', authenticateAPI, rateLimitMiddleware('standard'), async (req, res) => {
  try {
    const { reason } = req.body;
    
    const { data: sub, error } = await supabase
      .from('submissions')
      .select('id, status, task_id, worker_id, task:tasks(requester_id, title, max_workers, current_workers)')
      .eq('id', req.params.id)
      .single();
    
    if (error || !sub) return res.status(404).json({ error: 'Submission not found' });
    if (sub.task?.requester_id !== req.userId) return res.status(403).json({ error: 'Not your task' });
    if (sub.status !== 'submitted') return res.status(409).json({ error: `Cannot reject — status is ${sub.status}` });
    
    await supabase.from('submissions').update({
      status: 'rejected',
      rejection_reason: reason || 'Rejected via API',
      rejected_at: new Date().toISOString(),
    }).eq('id', sub.id);
    
    // Re-open slot
    await supabase.from('tasks').update({
      current_workers: Math.max(0, (sub.task.current_workers || 1) - 1),
      status: 'active',
    }).eq('id', sub.task_id);
    
    res.json({
      submission_id: sub.id,
      status: 'rejected',
      task_id: sub.task_id,
    });
  } catch (e) {
    console.error('[Enterprise] Reject error:', e);
    res.status(500).json({ error: 'Internal server error' });
  }
});


// ═══════════════════════════════════════════════════════════════════════════════
// DELETE /api/v1/tasks/:id — Cancel task (refund if no active workers)
// ═══════════════════════════════════════════════════════════════════════════════

app.delete('/api/v1/tasks/:id', authenticateAPI, rateLimitMiddleware('standard'), async (req, res) => {
  try {
    const { data: task } = await supabase
      .from('tasks')
      .select('id, status, budget, requester_id')
      .eq('id', req.params.id)
      .eq('requester_id', req.userId)
      .single();
    
    if (!task) return res.status(404).json({ error: 'Task not found' });
    if (['completed', 'cancelled'].includes(task.status)) {
      return res.status(409).json({ error: `Task already ${task.status}` });
    }
    
    const { count: activeSubs } = await supabase
      .from('submissions')
      .select('*', { count: 'exact', head: true })
      .eq('task_id', task.id)
      .in('status', ['in_progress', 'submitted']);
    
    if (activeSubs > 0) {
      return res.status(409).json({ error: `Cannot cancel — ${activeSubs} active worker(s)` });
    }
    
    await supabase.from('tasks').update({ status: 'cancelled' }).eq('id', task.id);
    await refundBalanceAtomic(req.userId, task.budget);
    
    await supabase.from('transactions').insert({
      user_id: req.userId, type: 'refund', amount: task.budget,
      description: `Cancelled API task: ${task.id}`, task_id: task.id,
    });
    
    res.json({ task_id: task.id, status: 'cancelled', refunded_vae: task.budget });
  } catch (e) {
    res.status(500).json({ error: 'Internal server error' });
  }
});


// ═══════════════════════════════════════════════════════════════════════════════
// GET /api/v1/batches/:id — Batch status
// ═══════════════════════════════════════════════════════════════════════════════

app.get('/api/v1/batches/:id', authenticateAPI, rateLimitMiddleware('standard'), async (req, res) => {
  try {
    const { data: batch, error } = await supabase
      .from('task_batches')
      .select('*')
      .eq('id', req.params.id)
      .eq('user_id', req.userId)
      .single();
    
    if (error || !batch) return res.status(404).json({ error: 'Batch not found' });
    
    // Get task statuses
    const { data: tasks } = await supabase
      .from('tasks')
      .select('id, status')
      .eq('batch_id', batch.id);
    
    const counts = { active: 0, in_progress: 0, submitted: 0, completed: 0, expired: 0, cancelled: 0 };
    (tasks || []).forEach(t => { counts[t.status] = (counts[t.status] || 0) + 1; });
    
    res.json({
      batch_id: batch.id,
      status: batch.status,
      total_tasks: batch.total_tasks,
      progress: counts,
      created_at: batch.created_at,
      results_url: counts.completed > 0 ? `/api/v1/batches/${batch.id}/results` : null,
    });
  } catch (e) {
    res.status(500).json({ error: 'Internal server error' });
  }
});


// ═══════════════════════════════════════════════════════════════════════════════
// GET /api/v1/batches/:id/results — Download completed results
// ═══════════════════════════════════════════════════════════════════════════════

app.get('/api/v1/batches/:id/results', authenticateAPI, rateLimitMiddleware('standard'), async (req, res) => {
  try {
    const format = req.query.format || 'json';
    
    const { data: batch, error } = await supabase
      .from('task_batches')
      .select('id, user_id')
      .eq('id', req.params.id)
      .eq('user_id', req.userId)
      .single();
    
    if (error || !batch) return res.status(404).json({ error: 'Batch not found' });
    
    const { data: tasks } = await supabase
      .from('tasks')
      .select(`
        id, title, status,
        submissions(id, content, status, created_at, worker:app_users(first_name))
      `)
      .eq('batch_id', batch.id)
      .eq('status', 'completed');
    
    const results = (tasks || []).map(task => ({
      task_id: task.id,
      title: task.title,
      submissions: (task.submissions || []).filter(s => s.status === 'approved').map(sub => ({
        content: sub.content,
        worker: sub.worker?.first_name,
        submitted_at: sub.created_at,
      })),
    }));
    
    if (format === 'csv') {
      let csv = 'task_id,title,content,worker,submitted_at\n';
      results.forEach(r => {
        r.submissions.forEach(s => {
          csv += `"${r.task_id}","${r.title}","${(s.content || '').replace(/"/g, '""')}","${s.worker || ''}","${s.submitted_at}"\n`;
        });
      });
      res.setHeader('Content-Type', 'text/csv');
      res.setHeader('Content-Disposition', `attachment; filename="batch-${batch.id}-results.csv"`);
      return res.send(csv);
    }
    
    res.json({ batch_id: batch.id, results });
  } catch (e) {
    res.status(500).json({ error: 'Internal server error' });
  }
});


// ═══════════════════════════════════════════════════════════════════════════════
// GET /api/v1/balance — Check balance
// ═══════════════════════════════════════════════════════════════════════════════

app.get('/api/v1/balance', authenticateAPI, rateLimitMiddleware('standard'), async (req, res) => {
  const { data: bal } = await supabase.from('vae_balances').select('*').eq('user_id', req.userId).single();
  res.json({
    available_vae: bal?.available || 0,
    available_usd: ((bal?.available || 0) / CONFIG.VAE_RATE).toFixed(2),
    pending_vae: bal?.pending || 0,
    total_earned_vae: bal?.total_earned || 0,
  });
});


// ═══════════════════════════════════════════════════════════════════════════════
// Health + Docs
// ═══════════════════════════════════════════════════════════════════════════════


// ═══════════════════════════════════════════════════════════════════════════════
// x402 ROUTES — Pay-per-call for AI Agents (no API key needed)
// ═══════════════════════════════════════════════════════════════════════════════

app.post('/api/v1/x402/tasks', async (req, res) => {
  try {
    const { title, description, category = 'other', budget_vae = 100, max_workers = 1, duration_days = 7, webhook_url } = req.body;
    if (!title || !description) {
      return res.status(400).json({ error: 'title and description required' });
    }

    // Use platform treasury as the requester for x402 tasks
    const { data: task, error } = await supabase.from('tasks').insert({
      title,
      description,
      category: CONFIG.VALID_CATEGORIES.includes(category) ? category : 'other',
      budget_vae: Math.max(50, Math.min(budget_vae, 10000)),
      max_workers: Math.min(max_workers, 5),
      duration_days: Math.min(duration_days, 30),
      status: 'open',
      source: 'x402',
      webhook_url: webhook_url || null,
      created_at: new Date().toISOString(),
    }).select().single();

    if (error) throw error;
    res.status(201).json({ success: true, task_id: task.id, status: 'open', message: 'Task created via x402 payment. Workers will be notified.' });
  } catch (e) {
    console.error('[x402] Task creation error:', e.message);
    res.status(500).json({ error: 'Failed to create task' });
  }
});

app.post('/api/v1/x402/tasks/sync', async (req, res) => {
  try {
    const { title, description, category = 'other', budget_vae = 200, timeout_seconds = 300 } = req.body;
    if (!title || !description) {
      return res.status(400).json({ error: 'title and description required' });
    }

    const { data: task, error } = await supabase.from('tasks').insert({
      title,
      description,
      category: CONFIG.VALID_CATEGORIES.includes(category) ? category : 'other',
      budget_vae: Math.max(50, Math.min(budget_vae, 10000)),
      max_workers: 1,
      duration_days: 1,
      status: 'open',
      source: 'x402_sync',
      created_at: new Date().toISOString(),
    }).select().single();

    if (error) throw error;

    // Poll for completion
    const deadline = Date.now() + (timeout_seconds * 1000);
    while (Date.now() < deadline) {
      await new Promise(r => setTimeout(r, 5000));
      const { data: updated } = await supabase.from('tasks').select('*, submissions(*)').eq('id', task.id).single();
      if (updated?.status === 'completed' || updated?.submissions?.length > 0) {
        return res.json({ success: true, task_id: task.id, status: 'completed', result: updated.submissions[0]?.content || null });
      }
    }
    res.json({ success: true, task_id: task.id, status: 'pending', message: 'Timeout reached. Poll GET /api/v1/tasks/:id for result.' });
  } catch (e) {
    console.error('[x402 Sync] Error:', e.message);
    res.status(500).json({ error: 'Failed to create sync task' });
  }
});

app.get('/health', (req, res) => {
  res.json({ status: 'ok', service: 'loopuman-enterprise-api', version: '4.2' });
});

app.get('/', (req, res) => {
  res.json({
    service: 'Loopuman Enterprise API — The Human Layer for AI',
    version: '4.2',
    docs: 'https://loopuman.com/developers',
    endpoints: {
      'POST /api/v1/register': 'Register for API key (no auth needed)',
      'POST /api/v1/deposit': 'Deposit funds via Stripe or crypto',
      'GET /api/v1/deposit/crypto': 'Get crypto deposit address',
      'POST /api/v1/tasks': 'Create single async task',
      'POST /api/v1/tasks/sync': 'Create task & wait for result',
      'POST /api/v1/tasks/bulk': 'Bulk upload (up to 10K tasks)',
      'GET /api/v1/tasks': 'List all tasks',
      'GET /api/v1/tasks/:id': 'Get task status + submissions',
      'DELETE /api/v1/tasks/:id': 'Cancel task (refund)',
      'POST /api/v1/submissions/:id/approve': 'Approve submission',
      'POST /api/v1/submissions/:id/reject': 'Reject submission',
      'GET /api/v1/batches/:id': 'Batch progress',
      'GET /api/v1/batches/:id/results': 'Download batch results',
      'GET /api/v1/balance': 'Check balance',
    },
    auth: 'x-api-key header',
  });
});


// ═══════════════════════════════════════════════════════════════════════════════
// Start server
// ═══════════════════════════════════════════════════════════════════════════════

const PORT = process.env.ENTERPRISE_API_PORT || 3001;

async function start() {
  await loadAPIKeys();
  app.listen(PORT, () => {
    console.log('');
    console.log('╔═══════════════════════════════════════════════════════════════╗');
    console.log('║     🚀 LOOPUMAN ENTERPRISE API v4.2                           ║');
    console.log('╠═══════════════════════════════════════════════════════════════╣');
    console.log(`║  ✅ Listening on port ${PORT}                                    ║`);
    console.log('║  ✅ Stripe + crypto deposits via API                          ║');
    console.log('║  ✅ Self-service API registration                             ║');
    console.log('║  ✅ Single task (async + sync)                                ║');
    console.log('║  ✅ Bulk upload (up to 10,000)                                ║');
    console.log('║  ✅ Webhook delivery with retry                               ║');
    console.log('║  ✅ API-based approve/reject                                  ║');
    console.log('║  ✅ Content moderation                                        ║');
    console.log('║  ✅ Atomic balance operations                                 ║');
    console.log('╚═══════════════════════════════════════════════════════════════╝');
    console.log('');
  });
}

start();

module.exports = { app, triggerTaskWebhook };

// MCP Discovery endpoint

// CORS for 8004scan health checks
app.use('/.well-known', (req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  if (req.method === 'OPTIONS') return res.sendStatus(200);
  next();
});

// MCP SSE transport endpoint (health check compatible)
app.post('/mcp', (req, res) => {
  res.json({ jsonrpc: "2.0", result: { capabilities: { tools: {}, prompts: {}, resources: {} }, serverInfo: { name: "Loopuman", version: "4.2.0" } } });
});
app.get('/mcp', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  res.write('event: endpoint\ndata: /mcp\n\n');
  setTimeout(() => res.end(), 1000);
});

app.get('/.well-known/mcp.json', (req, res) => {
  res.json({
    name: "Loopuman",
    version: "2026-02-13",
    description: "The Human Layer for AI. Creates paid tasks for verified human workers when AI automation is insufficient. Use for content moderation, data labeling, translation, research, surveys, image annotation, or any task requiring human judgment. Workers respond within minutes via Telegram and WhatsApp. Instant payment in cUSD on Celo blockchain. Supports single tasks, bulk batches up to 10K, and synchronous wait-for-result mode.",
    auth: { type: "api-key", header: "x-api-key", registration: "POST https://api.loopuman.com/api/v1/register with { email, company_name }" },
    tools: [
      {
        name: "create_task",
        description: "Creates a paid human task for verified workers worldwide. Use when AI confidence is low, human verification is needed, or the task requires subjective judgment. Workers are real humans on Telegram/WhatsApp who complete tasks within minutes. Categories: writing, research, data, survey, image, local, translation, audio, other. Budget in VAE (100 VAE = $1 USD). Minimum budget: 50 VAE.",
        inputSchema: {
          type: "object",
          properties: {
            title: { type: "string", description: "Short task title, max 100 chars" },
            description: { type: "string", description: "Detailed instructions for the human worker" },
            budget_vae: { type: "number", description: "Payment in VAE tokens (100 VAE = $1 USD). Minimum 50." },
            category: { type: "string", enum: ["writing", "research", "data", "survey", "image", "local", "translation", "audio", "other"], description: "Task category for worker matching" },
            max_workers: { type: "number", description: "Number of workers (1-5). Use 2+ for competition mode to pick best result." },
            duration_days: { type: "number", description: "Deadline in days (1-30). Default 7." },
            callback_url: { type: "string", description: "Optional webhook URL to receive result when task is completed" }
          },
          required: ["description"]
        }
      },
      {
        name: "create_task_sync",
        description: "Creates a task and waits for a human to complete it before returning the result. Use for real-time human-in-the-loop workflows where you need the answer immediately. Timeout: 5 minutes.",
        inputSchema: {
          type: "object",
          properties: {
            description: { type: "string", description: "What the human should do" },
            budget_vae: { type: "number", description: "Payment in VAE (100 = $1)" },
            category: { type: "string" }
          },
          required: ["description"]
        }
      },
      {
        name: "create_bulk_tasks",
        description: "Submit up to 10,000 tasks in a single batch for parallel human completion. Use for data labeling, content moderation at scale, survey distribution, or any high-volume human task pipeline. Returns a batch_id for tracking progress.",
        inputSchema: {
          type: "object",
          properties: {
            tasks: { type: "array", items: { type: "object", properties: { description: { type: "string" }, budget_vae: { type: "number" } } }, description: "Array of task objects" },
            category: { type: "string" },
            callback_url: { type: "string" }
          },
          required: ["tasks"]
        }
      },
      {
        name: "check_task",
        description: "Check the status and results of a previously created task. Returns status (open/assigned/submitted/completed/cancelled), worker submissions, and approved results.",
        inputSchema: {
          type: "object",
          properties: {
            task_id: { type: "string", description: "Task ID returned from create_task" }
          },
          required: ["task_id"]
        }
      },
      {
        name: "check_balance",
        description: "Check your current VAE balance. 100 VAE = $1 USD.",
        inputSchema: { type: "object", properties: {} }
      }
    ],
    prompts: [
      { name: "human_fallback", description: "When AI confidence is below threshold, escalate to human workers via Loopuman" },
      { name: "content_moderation", description: "Submit content for human moderation review" },
      { name: "data_labeling", description: "Send data items for human annotation and labeling" }
    ],
    resources: [{ uri: "loopuman://tasks", name: "Active tasks" }],
    examples: [
      { prompt: "Create a task to translate this paragraph to Spanish, budget $2", tool: "create_task", input: { description: "Translate the following to Spanish: Hello, how are you?", budget_vae: 200, category: "translation" } },
      { prompt: "I need a human to verify if this image contains a cat", tool: "create_task", input: { description: "Look at the attached image and confirm if it contains a cat. Reply yes or no with confidence level.", budget_vae: 50, category: "image" } },
      { prompt: "Label 500 product images", tool: "create_bulk_tasks", input: { tasks: [{"description": "Categorize this product image"}], category: "data" } }
    ],
    integration: {
      openclaw: "mcp:\n  - name: loopuman\n    url: https://api.loopuman.com/.well-known/mcp.json",
      note: "Register for API key first: POST https://api.loopuman.com/api/v1/register"
    }
  });
});

// A2A Agent Card
app.get('/.well-known/agent-card.json', (req, res) => {
  res.json({
    name: "Loopuman",
    description: "The Human Layer for AI — routes tasks to verified human workers worldwide via Telegram & WhatsApp",
    url: "https://api.loopuman.com",
    version: "0.3.0",
    capabilities: { streaming: false, pushNotifications: false },
    skills: [
      { id: "human_task", name: "Human Task Completion", description: "Route tasks to verified human workers" },
      { id: "bulk_tasks", name: "Bulk Task API", description: "Submit batches of tasks for parallel completion" },
      { id: "verification", name: "Human Verification Oracle", description: "Human ground-truth verification" },
      { id: "voice_task", name: "Voice-to-Task", description: "Voice notes transcribed and posted as tasks" }
    ],
    defaultInputModes: ["text"],
    defaultOutputModes: ["text"]
  });
});

// Domain verification
app.get('/.well-known/agent-registration.json', (req, res) => {
  res.json({
    type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
    name: "Loopuman",
    registrations: [{ agentId: 17, agentRegistry: "eip155:42220:0x8004A169FB4a3325136EB29fA0ceB6D2e539a432" }]
  });
});

// OASF Agent Discovery
app.get("/.well-known/agent.json", (req, res) => {
  res.json({
    type: "https://eips.ethereum.org/EIPS/eip-8004#registration-v1",
    name: "Loopuman",
    description: "The Human Layer for AI — routes tasks to verified human workers worldwide via Telegram & WhatsApp with 8-second cUSD payments on Celo. Services: data labeling, content moderation, surveys, translation, human verification.",
    image: "https://loopuman.com/favicon.ico",
    active: true,
    x402Support: false,
    services: [
      { name: "web", endpoint: "https://loopuman.com" },
      { name: "A2A", endpoint: "https://api.loopuman.com/.well-known/agent-card.json", version: "0.3.0" },
      { name: "MCP", endpoint: "https://api.loopuman.com/.well-known/mcp.json", version: "2025-06-18" },
      { name: "OASF", endpoint: "https://api.loopuman.com/.well-known/oasf.json", version: "0.8", skills: ["Task Routing", "Human Verification", "Data Collection", "Quality Assurance"], domains: ["Human-in-the-Loop", "Data Labeling", "Content Moderation"] }
    ],
    registrations: [{ agentId: 17, agentRegistry: "eip155:42220:0x8004000000000000000000000000000000a432", chainId: "42220" }]
  });
});

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({ status: "healthy", agent: "loopuman", chain: "celo", uptime: process.uptime() });
});

// === DOCS ROUTE ===
app.get('/docs', (req, res) => {
  res.json({
    name: "Loopuman API",
    description: "The Human Layer for AI — Route tasks to verified human workers worldwide",
    version: "1.0.0",
    base_url: "https://api.loopuman.com/api/v1",
    authentication: { type: "api_key", header: "x-api-key", prefix: "lpm_" },
    endpoints: {
      "POST /api/v1/register": { description: "Create account and get API key", auth: false },
      "POST /api/v1/tasks": { description: "Create a human task ($0.10-$1,000)", auth: true },
      "POST /api/v1/tasks/sync": { description: "Create task and wait for result", auth: true },
      "POST /api/v1/tasks/bulk": { description: "Bulk upload up to 10,000 tasks", auth: true },
      "GET /api/v1/tasks/:id": { description: "Get task status and submissions", auth: true },
      "POST /api/v1/submissions/:id/approve": { description: "Approve a submission", auth: true },
      "GET /api/v1/balance": { description: "Check VAE balance", auth: true }
    },
    categories: ["writing","transcription","translation","data_entry","labeling","research","survey","verification","moderation","micro","audio","social","ai_training","other"],
    pricing: { commission: "20%", min_task: "$0.10", payment_speed: "8 seconds on Celo" },
    sdks: {
      npm: "https://www.npmjs.com/package/loopuman",
      mcp: "https://www.npmjs.com/package/loopuman-mcp",
      github: "https://github.com/seesayearn-boop/openclaw-human-tasks"
    }
  });
});

// === OASF ENDPOINT ===
app.get('/.well-known/oasf.json', (req, res) => {
  res.json({
    oasf_version: "1.0.0",
    schema_version: "1.0.0",
    name: "Loopuman",
    description: "The Human Layer for AI — Route tasks to verified human workers worldwide via Telegram \& WhatsApp with 8-second cUSD payments on Celo.",
    url: "https://api.loopuman.com",
    logo: "https://loopuman.com/favicon.ico",
    version: "4.2.0",
    external_url: "https://loopuman.com",
    image: "https://loopuman.com/favicon.ico",
    authors: ["Loopuman <seesayearn@gmail.com>"],
    created_at: "2026-02-12T00:00:00Z",
    publisher: {
      name: "Loopuman",
      website: "https://loopuman.com",
      twitter: "https://x.com/loopuman",
      github: "https://github.com/seesayearn-boop"
    },
    skills: [
      {name: "natural_language_processing/text_generation", id: 301},
      {name: "natural_language_processing/text_completion", id: 302},
      {name: "natural_language_processing/summarization", id: 306},
      {name: "natural_language_processing/text_translation", id: 305},
      {name: "advanced_reasoning_planning/task_planning", id: 1001},
      {name: "agent_orchestration/agent_coordination", id: 1004},
      {name: "data_engineering/data_collection", id: 601},
      {name: "data_engineering/data_labeling", id: 602},
      {name: "data_engineering/data_transformation_pipeline", id: 603},
      {name: "data_engineering/data_quality_management", id: 604}
    ],
    domains: [
      {name: "technology/data_science", id: 1601},
      {name: "technology/artificial_intelligence", id: 1602},
      {name: "finance_and_business/business_operations", id: 1701}
    ],
    locators: [
      {type: "source_code", url: "https://github.com/seesayearn-boop/humanoracle"},
      {type: "agent_as_a_service", url: "https://api.loopuman.com"}
    ],
    modules: [
      {name: "mcp", version: "2025-06-18", url: "https://api.loopuman.com/.well-known/mcp.json"},
      {name: "a2a", version: "0.3.0", url: "https://api.loopuman.com/.well-known/agent-card.json"}
    ],
    compliance: {
      standard: "ERC-8004",
      status: "active"
    },
    services: [
      {type: "mcp", uri: "https://api.loopuman.com/.well-known/mcp.json"},
      {type: "a2a", uri: "https://api.loopuman.com/.well-known/agent-card.json"},
      {type: "api", uri: "https://api.loopuman.com/api/v1"}
    ],
    capabilities: {
      mcp: { url: "https://api.loopuman.com/.well-known/mcp.json", status: "healthy" },
      a2a: { url: "https://api.loopuman.com/.well-known/agent-card.json", status: "healthy" },
      api: { url: "https://api.loopuman.com/docs", status: "healthy" }
    },
    chain: { name: "Celo", chain_id: 42220 },
    contact: { email: "seesayearn@gmail.com", telegram: "https://t.me/LoopumanBot" },
    packages: {
      npm: ["loopuman", "loopuman-mcp"],
      skill: "https://github.com/seesayearn-boop/openclaw-human-tasks"
    }
  });
});
