const mongoose = require('mongoose');
const { createProviderRegistry, createProviderRegistryForTenant } = require('@/services/integrations/providerRegistry');
const { requestModel } = require('./llmGateway');
const { orchestrateWithHermes } = require('./hermes');
const { calculateCost } = require('./costService');
const { assertToolAccess, requiresApproval, executeTool, summarizeOutput } = require('./toolRegistry');
const { createNotification } = require('@/services/notifications/service');

const createStep = (payload) => mongoose.model('AgentRunStep').create(payload);
const Step = () => mongoose.model('AgentRunStep');
const Approval = () => mongoose.model('AgentApproval');
const Run = () => mongoose.model('AgentRun');

const queueAgentRun = async ({ agent, actor, tenant, input = {}, trigger = 'manual', testMode = false }) => {
  const run = await mongoose.model('AgentRun').create({ tenant, createdBy: actor?._id, assignedTo: actor?._id, agent: agent._id, agentVersion: agent.version, trigger, input, testMode, status: 'queued' });
  await mongoose.model('Job').create({ tenant, createdBy: actor?._id, type: 'agent.run', payload: { run: run._id, actor: actor?._id }, status: 'queued' });
  return run;
};

const addUsage = (run, usage = {}, cost = { amount: 0, currency: 'NGN' }) => {
  run.usage = {
    inputTokens: Number(run.usage?.inputTokens || 0) + Number(usage.inputTokens || 0),
    outputTokens: Number(run.usage?.outputTokens || 0) + Number(usage.outputTokens || 0),
    totalTokens: Number(run.usage?.totalTokens || 0) + Number(usage.totalTokens || 0),
  };
  run.cost = {
    amount: Number(run.cost?.amount || 0) + Number(cost.amount || 0),
    currency: cost.currency || run.cost?.currency || 'NGN',
  };
};

const buildKnowledgeContext = async ({ agent }) => {
  const sourceIds = (agent.knowledgeSources || []).map((item) => item?._id || item).filter(Boolean);
  if (!sourceIds.length || !mongoose.models.KnowledgeChunk) return [];
  return mongoose.model('KnowledgeChunk').find({ source: { $in: sourceIds }, removed: false }).limit(8).lean();
};

const ensurePlan = async ({ run, agent, actor, providers }) => {
  if (run.plan?.steps?.length) return run.plan;
  run.status = 'planning';
  await run.save();
  const knowledge = await buildKnowledgeContext({ agent });
  const history = await Step().find({ run: run._id, removed: false }).sort({ sequence: 1 }).lean();
  const orchestration = await orchestrateWithHermes({ agent, input: run.input || {}, history, knowledge, actor, providers });
  run.plan = orchestration.plan;
  addUsage(run, orchestration.usage || {}, calculateCost({ usage: orchestration.usage, provider: orchestration.provider }));
  await run.save();
  await createStep({
    tenant: run.tenant,
    createdBy: run.createdBy,
    run: run._id,
    sequence: 0,
    stepId: 'plan',
    attempt: 1,
    kind: 'model',
    provider: orchestration.provider,
    input: { objective: run.input?.objective, tools: agent.tools || [] },
    output: orchestration.plan,
    outputSummary: orchestration.plan.goal,
    usage: orchestration.usage || {},
    cost: calculateCost({ usage: orchestration.usage, provider: orchestration.provider }),
    status: 'succeeded',
    startedAt: new Date(),
    completedAt: new Date(),
  });
  return run.plan;
};

const processAgentRun = async ({ runId, actor, providers }) => {
  const run = await Run().findById(runId);
  if (!run || ['cancelled', 'succeeded'].includes(run.status)) return run;
  const resolvedProviders =
    providers || (await createProviderRegistryForTenant({ tenantId: run.tenant })) || createProviderRegistry(process.env);
  const agent = await mongoose.model('AgentDefinition').findOne({ _id: run.agent, tenant: run.tenant, removed: false });
  if (!agent) throw new Error('Agent definition not found');
  run.status = 'running'; run.startedAt ||= new Date(); await run.save();
  const plan = await ensurePlan({ run, agent, actor, providers: resolvedProviders });
  const runActor = actor || (run.createdBy ? await mongoose.model('Admin').findById(run.createdBy).populate('roleRefs') : null);
  for (let index = run.currentStep || 0; index < (plan.steps || []).length; index += 1) {
    const toolStep = plan.steps[index];
    const definition = assertToolAccess({ tool: toolStep.tool, actor: runActor || {} });
    let step = await Step().findOne({ run: run._id, sequence: index + 1, removed: false });
    if (step?.status === 'succeeded') {
      run.currentStep = index + 1;
      await run.save();
      continue;
    }
    if (!step) {
      step = await createStep({
        tenant: run.tenant,
        createdBy: run.createdBy,
        run: run._id,
        sequence: index + 1,
        stepId: toolStep.id,
        attempt: 1,
        kind: 'tool',
        tool: toolStep.tool,
        input: run.input,
        plannedInput: toolStep.input,
        requiresApproval: toolStep.requiresApproval || requiresApproval({ definition, agent, testMode: run.testMode }),
        provider: 'hermes',
        status: 'queued',
      });
    }
    const approvedStepIds = new Set(run.input?._approvedStepIds || []);
    if (step.requiresApproval && !approvedStepIds.has(step.stepId)) {
      step.status = 'needs_approval';
      await step.save();
      let approval = await Approval().findOne({ run: run._id, step: step._id, status: 'pending', removed: false });
      if (!approval) {
        approval = await Approval().create({ tenant: run.tenant, createdBy: run.createdBy, run: run._id, step: step._id, tool: toolStep.tool, riskLevel: definition.riskLevel, payloadPreview: toolStep.input, expiresAt: new Date(Date.now() + 86400000) });
        step.approvalId = approval._id;
        await step.save();
        await createNotification({
          tenantId: run.tenant,
          actorId: run.createdBy,
          recipientId: run.createdBy,
          type: 'agent.approval.requested',
          title: `${agent.name} needs approval`,
          message: `Approve ${toolStep.tool} before this agent can continue.`,
          severity: 'warning',
          entityType: 'AgentRun',
          entityId: run._id,
          actionUrl: `/ai-studio/runs`,
          eventKey: `agent-approval:${approval._id}`,
          channels: ['in_app', 'email', 'whatsapp'],
        });
      }
      run.status = 'needs_approval'; run.currentStep = index; await run.save();
      return run;
    }
    if (approvedStepIds.has(step.stepId)) {
      run.input = { ...run.input, _approvedStepIds: [...approvedStepIds].filter((key) => key !== step.stepId) };
    }
    try {
      const started = Date.now();
      step.status = 'running';
      step.startedAt = new Date();
      await step.save();
      const stepInput = { ...(toolStep.input || run.input || {}), idempotencyKey: `${run._id}:${step.stepId}:${step.attempt}` };
      const output = await executeTool({ tool: toolStep.tool, input: stepInput, tenant: run.tenant, actor: runActor, testMode: run.testMode, providers: resolvedProviders });
      step.output = output;
      step.outputSummary = summarizeOutput(toolStep.tool, output);
      step.durationMs = Date.now() - started;
      step.status = 'succeeded';
      step.completedAt = new Date();
      await step.save();
      run.currentStep = index + 1; await run.save();
    } catch (error) {
      step.status = 'failed'; step.error = error.message; step.completedAt = new Date(); await step.save();
      await createNotification({
        tenantId: run.tenant,
        actorId: run.createdBy,
        recipientId: run.createdBy,
        type: 'agent.run.failed',
        title: `${agent.name} failed`,
        message: `${toolStep.tool} failed: ${error.message}`,
        severity: 'error',
        entityType: 'AgentRun',
        entityId: run._id,
        actionUrl: '/ai-studio/runs',
        eventKey: `agent-run-failed:${run._id}:${step.stepId}:${step.attempt}`,
        channels: ['in_app', 'email', 'whatsapp'],
      });
      throw error;
    }
  }

  const result = await requestModel({ policy: { ...agent.modelPolicy, provider: agent.modelPolicy?.provider === 'hermes' ? 'openai' : agent.modelPolicy?.provider }, action: 'agentRun', instruction: plan.finalInstruction || agent.instructions, prompt: JSON.stringify(run.input), providers: resolvedProviders });
  const cost = calculateCost({ usage: result.usage, provider: result.provider });
  await createStep({ tenant: run.tenant, createdBy: run.createdBy, run: run._id, sequence: (plan.steps || []).length + 1, stepId: 'final-output', attempt: 1, kind: 'output', provider: result.provider, output: { content: result.content, provider: result.provider }, outputSummary: 'Final response generated', usage: result.usage, cost, status: 'succeeded', startedAt: new Date(), completedAt: new Date() });
  addUsage(run, result.usage, cost);
  run.output = { content: result.content, provider: result.provider };
  run.status = 'succeeded'; run.completedAt = new Date(); await run.save();
  await createNotification({
    tenantId: run.tenant,
    actorId: run.createdBy,
    recipientId: run.createdBy,
    type: 'agent.run.completed',
    title: `${agent.name} completed`,
    message: `The run finished with ${run.currentStep} step(s) and ${run.usage?.totalTokens || 0} tokens.`,
    severity: 'success',
    entityType: 'AgentRun',
    entityId: run._id,
    actionUrl: '/ai-studio/runs',
    eventKey: `agent-run-complete:${run._id}`,
    channels: ['in_app'],
  });
  return run;
};

module.exports = { queueAgentRun, processAgentRun };
