// node-cron is cloud-only — dynamically imported so local installs don't crash
let cron = null;
try { cron = (await import('node-cron')).default; } catch { /* not installed locally */ }

import { workflowDb, webhookDb } from '../database/db.js';
import * as composioService from './composio.js';

const activeJobs = new Map(); // workflowId -> cron task

/**
 * Execute a single workflow's steps (same logic as the /run endpoint).
 * Returns { success, results, error }
 */
async function executeWorkflow(workflow) {
  const steps = typeof workflow.steps === 'string' ? JSON.parse(workflow.steps) : workflow.steps;
  if (!steps.length) return { success: false, error: 'No steps' };

  const run = await workflowDb.createRun(workflow.id, workflow.user_id, steps.length);
  const results = [];
  let lastOutput = null;

  for (let i = 0; i < steps.length; i++) {
    const step = steps[i];
    try {
      let stepResult;

      if (step.type === 'webhook') {
        const webhookId = step.config?.webhookId;
        if (!webhookId) throw new Error('No webhook configured');

        const webhook = await webhookDb.getById(Number(webhookId), workflow.user_id);
        if (!webhook) throw new Error('Webhook not found');

        let parsedHeaders = {};
        try { parsedHeaders = JSON.parse(webhook.headers || '{}'); } catch { /* ignore */ }

        const controller = new AbortController();
        const timeout = setTimeout(() => controller.abort(), 15000);

        const fetchOptions = {
          method: webhook.method,
          headers: {
            'Content-Type': 'application/json',
            'User-Agent': 'UpfynAI-Scheduler/1.0',
            ...parsedHeaders
          },
          signal: controller.signal
        };

        if (['POST', 'PUT', 'PATCH'].includes(webhook.method)) {
          const payload = {
            workflow_id: workflow.id,
            workflow_name: workflow.name,
            step_index: i,
            step_label: step.label,
            previous_output: lastOutput,
            scheduled: true,
            timestamp: new Date().toISOString()
          };
          if (step.config?.payloadTemplate) {
            try { Object.assign(payload, JSON.parse(step.config.payloadTemplate)); } catch { /* ignore */ }
          }
          fetchOptions.body = JSON.stringify(payload);
        }

        const response = await fetch(webhook.url, fetchOptions);
        clearTimeout(timeout);

        const contentType = response.headers.get('content-type') || '';
        let body;
        if (contentType.includes('application/json')) {
          body = await response.json();
        } else {
          body = await response.text();
          if (body.length > 5000) body = body.slice(0, 5000) + '...';
        }

        await webhookDb.updateLastTriggered(webhook.id);
        stepResult = { status: response.status, body };
        lastOutput = body;

      } else if (step.type === 'ai-prompt') {
        stepResult = { type: 'ai-prompt', prompt: step.config?.prompt || '', note: 'Scheduled AI prompts require active session' };
        lastOutput = step.config?.prompt;

      } else if (step.type === 'delay') {
        const seconds = Math.min(step.config?.seconds || 1, 30);
        await new Promise(resolve => setTimeout(resolve, seconds * 1000));
        stepResult = { delayed: seconds };

      } else if (step.type === 'integration') {
        const toolSlug = step.config?.toolSlug;
        if (!toolSlug) throw new Error('No tool configured for integration step');
        if (!(await composioService.isAvailable())) throw new Error('Composio not configured');

        // Interpolate {{prev.field}} templates from lastOutput
        let args = { ...(step.config?.arguments || {}) };
        if (lastOutput && typeof lastOutput === 'object') {
          for (const [key, val] of Object.entries(args)) {
            if (typeof val === 'string' && val.includes('{{prev.')) {
              args[key] = val.replace(/\{\{prev\.(\w+)\}\}/g, (_, field) =>
                lastOutput[field] !== undefined ? String(lastOutput[field]) : ''
              );
            }
          }
        }

        const result = await composioService.executeTool(workflow.user_id, toolSlug, args);
        if (!result.success) throw new Error(result.error || 'Integration execution failed');
        stepResult = { toolSlug, ...result };
        lastOutput = result.data || result;

      } else {
        stepResult = { type: step.type, note: 'Unknown step type' };
      }

      results.push({ step: i, label: step.label, type: step.type, success: true, result: stepResult });
      await workflowDb.updateRun(run.id, { status: 'running', stepsCompleted: i + 1 });

    } catch (stepError) {
      results.push({ step: i, label: step.label, type: step.type, success: false, error: stepError.message });
      await workflowDb.updateRun(run.id, { status: 'failed', stepsCompleted: i, error: `Step ${i + 1} (${step.label}): ${stepError.message}` });
      await workflowDb.updateLastRun(workflow.id);
      return { success: false, run_id: run.id, results, error: `Step ${i + 1} failed: ${stepError.message}` };
    }
  }

  await workflowDb.updateRun(run.id, { status: 'completed', stepsCompleted: steps.length, result: results });
  await workflowDb.updateLastRun(workflow.id);
  return { success: true, run_id: run.id, results };
}

/**
 * Schedule a single workflow's cron job.
 */
function scheduleWorkflow(workflow) {
  if (!cron) return; // node-cron not available (local install)

  const id = workflow.id;

  // Stop existing job if any
  if (activeJobs.has(id)) {
    activeJobs.get(id).stop();
    activeJobs.delete(id);
  }

  if (!workflow.schedule || !workflow.schedule_enabled) return;

  // Validate cron expression
  if (!cron.validate(workflow.schedule)) {
    console.warn(`[Scheduler] Invalid cron for workflow ${id}: ${workflow.schedule}`);
    return;
  }

  const task = cron.schedule(workflow.schedule, async () => {
    try {
      await executeWorkflow(workflow);
    } catch {
      // workflow execution error
    }
  }, {
    timezone: workflow.schedule_timezone || 'UTC'
  });

  activeJobs.set(id, task);
}

/**
 * Load all scheduled workflows from DB and start their cron jobs.
 * Call this once at server startup.
 */
async function initScheduler() {
  try {
    const workflows = await workflowDb.getScheduled();
    for (const wf of workflows) {
      scheduleWorkflow(wf);
    }
  } catch (err) {
    // scheduler init error — silenced
  }
}

/**
 * Re-sync a specific workflow's schedule (call after create/update).
 */
async function refreshWorkflowSchedule(workflowId, userId) {
  try {
    const wf = await workflowDb.getById(workflowId, userId);
    if (wf) {
      scheduleWorkflow(wf);
    } else {
      // Workflow deleted — stop its job
      if (activeJobs.has(workflowId)) {
        activeJobs.get(workflowId).stop();
        activeJobs.delete(workflowId);
      }
    }
  } catch { /* ignore */ }
}

/**
 * Stop a workflow's cron job.
 */
function stopWorkflowSchedule(workflowId) {
  if (activeJobs.has(workflowId)) {
    activeJobs.get(workflowId).stop();
    activeJobs.delete(workflowId);
  }
}

export { initScheduler, refreshWorkflowSchedule, stopWorkflowSchedule, executeWorkflow };
