const mongoose = require('mongoose');
const { queueAgentRun, processAgentRun } = require('@/services/agents/runtime');
const { processDueAgentJobs } = require('@/services/agents/worker');
const { listSchedules, previewSchedule } = require('@/services/agents/scheduler');
const { requestModel } = require('@/services/agents/llmGateway');
const { agentTemplates, getAgentTemplate } = require('@/services/agents/templates');
const { toolDefinitions } = require('@/services/agents/toolRegistry');
const { canAccessAction } = require('@/services/platform/accessControl');
const { createProviderRegistryForTenant } = require('@/services/integrations/providerRegistry');

const scope = (req) => ({ tenant: req.tenantId, removed: false });
const json = (res, result, message) => res.status(200).json({ success: true, result, message });
const allowed = (req, res, permission) => canAccessAction(req.admin || {}, permission) || (res.status(403).json({ success: false, result: null, message: `Missing permission: ${permission}` }), false);

const templates = async (_req, res) => json(res, agentTemplates, 'Agent templates loaded');
const tools = async (_req, res) => json(res, toolDefinitions, 'Agent tools loaded');

const createFromTemplate = async (req, res) => {
  const template = getAgentTemplate(req.body.templateKey) || {};
  const agent = await mongoose.model('AgentDefinition').create({
    tenant: req.tenantId, createdBy: req.admin?._id, assignedTo: req.admin?._id,
    name: req.body.name || template.name || 'Custom AI Agent',
    description: req.body.description || template.description,
    specialistType: req.body.specialistType || template.key || 'custom',
    instructions: req.body.instructions || template.instructions || 'Assist the user while respecting permissions and approval policies.',
    tools: req.body.tools || template.tools || [], modelPolicy: { ...template.modelPolicy, ...req.body.modelPolicy },
    knowledgeSources: req.body.knowledgeSources || [], approvalPolicy: req.body.approvalPolicy,
    schedule: req.body.schedule, limits: req.body.limits,
  });
  return json(res, agent, 'Agent created');
};

const publish = async (req, res) => {
  if (!allowed(req, res, 'ai.agent.publish')) return;
  const Agent = mongoose.model('AgentDefinition');
  const agent = await Agent.findOne({ _id: req.params.id, ...scope(req) });
  if (!agent) return res.status(404).json({ success: false, message: 'Agent not found' });
  agent.version += 1; agent.status = 'published'; agent.publishedAt = new Date(); await agent.save();
  await mongoose.model('AgentVersion').create({ tenant: req.tenantId, createdBy: req.admin?._id, agent: agent._id, version: agent.version, snapshot: agent.toObject(), publishedBy: req.admin?._id });
  return json(res, agent, 'Agent published');
};

const version = async (req, res) => json(res, await mongoose.model('AgentVersion').find({ agent: req.params.id, ...scope(req) }).sort({ version: -1 }).lean(), 'Agent versions loaded');

const startRun = (testMode) => async (req, res) => {
  if (!allowed(req, res, 'ai.agent.run')) return;
  const agent = await mongoose.model('AgentDefinition').findOne({ _id: req.params.id, ...scope(req) });
  if (!agent) return res.status(404).json({ success: false, message: 'Agent not found' });
  const run = await queueAgentRun({ agent, actor: req.admin, tenant: req.tenantId, input: req.body || {}, trigger: testMode ? 'test' : 'manual', testMode });
  if (testMode || req.body?.runNow) {
    const providers = await createProviderRegistryForTenant({ tenantId: req.tenantId });
    await processAgentRun({ runId: run._id, actor: req.admin, providers });
  }
  return json(res, await mongoose.model('AgentRun').findById(run._id), testMode ? 'Test run completed' : 'Agent run queued');
};

const runDetail = async (req, res) => {
  const run = await mongoose.model('AgentRun').findOne({ _id: req.params.id, ...scope(req) }).lean();
  if (!run) return res.status(404).json({ success: false, message: 'Agent run not found' });
  const [steps, approvals] = await Promise.all([
    mongoose.model('AgentRunStep').find({ run: run._id, ...scope(req) }).sort({ sequence: 1 }).lean(),
    mongoose.model('AgentApproval').find({ run: run._id, ...scope(req) }).sort({ created: -1 }).lean(),
  ]);
  return json(res, { ...run, steps, approvals }, 'Agent run loaded');
};

const runs = async (req, res) => json(res, await mongoose.model('AgentRun').find(scope(req)).sort({ created: -1 }).limit(Number(req.query.limit || 50)).lean(), 'Agent runs loaded');

const calendar = async (req, res) => {
  const from = req.query.from ? new Date(req.query.from) : new Date(Date.now() - 7 * 86400000);
  const to = req.query.to ? new Date(req.query.to) : new Date(Date.now() + 60 * 86400000);
  const [agents, runs, approvals] = await Promise.all([
    mongoose.model('AgentDefinition').find({ ...scope(req), 'schedule.enabled': true }).lean(),
    mongoose.model('AgentRun').find({ ...scope(req), trigger: 'schedule', created: { $gte: from, $lte: to } }).sort({ created: -1 }).lean(),
    mongoose.model('AgentApproval').find({ ...scope(req), status: 'pending' }).lean(),
  ]);
  const result = agents.map((agent) => {
    const lastRun = runs.find((run) => String(run.agent) === String(agent._id));
    return {
      id: String(agent._id),
      type: 'agent_run',
      title: agent.name,
      start: agent.schedule?.nextRunAt,
      end: agent.schedule?.nextRunAt,
      status: agent.status,
      owner: req.admin?._id,
      riskLevel: approvals.some((item) => String(item.run) === String(lastRun?._id)) ? 'high' : 'low',
      linkedEntity: { entityType: 'AgentDefinition', entityId: agent._id },
      actionUrl: '/ai-studio/calendar',
      nextRunAt: agent.schedule?.nextRunAt,
      lastRunStatus: lastRun?.status,
      pendingApprovals: approvals.filter((item) => String(item.run) === String(lastRun?._id)).length,
      estimatedCost: agent.limits?.costBudget,
    };
  }).filter((item) => item.start && item.start >= from && item.start <= to);
  return json(res, result, 'Agent calendar loaded');
};

const schedule = async (req, res) => json(res, await listSchedules({ tenantId: req.tenantId }), 'Agent schedules loaded');

const schedulePreview = async (req, res) => {
  const agent = await mongoose.model('AgentDefinition').findOne({ _id: req.params.id, ...scope(req) }).lean();
  if (!agent) return res.status(404).json({ success: false, message: 'Agent not found' });
  return json(res, previewSchedule({ agent, expression: req.body?.expression, timezoneName: req.body?.timezone || agent.schedule?.timezone }), 'Schedule preview loaded');
};

const cancel = async (req, res) => {
  if (!allowed(req, res, 'ai.agent.run')) return;
  const run = await mongoose.model('AgentRun').findOneAndUpdate({ _id: req.params.id, ...scope(req), status: { $in: ['queued', 'running', 'needs_approval'] } }, { status: 'cancelled', completedAt: new Date() }, { new: true });
  await mongoose.model('Job').updateMany({ tenant: req.tenantId, 'payload.run': req.params.id, status: { $in: ['queued', 'running', 'waiting_approval'] } }, { status: 'cancelled' });
  return json(res, run, 'Agent run cancelled');
};

const decideApproval = async (req, res) => {
  if (!allowed(req, res, 'ai.approval.decide')) return;
  const approval = await mongoose.model('AgentApproval').findOne({ _id: req.params.id, ...scope(req), status: 'pending' });
  if (!approval) return res.status(404).json({ success: false, message: 'Pending approval not found' });
  approval.status = req.body.decision === 'approved' ? 'approved' : 'rejected'; approval.approver = req.admin?._id; approval.decidedAt = new Date(); approval.reason = req.body.reason; await approval.save();
  if (approval.status === 'approved') {
    await mongoose.model('AgentRunStep').findByIdAndUpdate(approval.step, { status: 'queued' });
    const run = await mongoose.model('AgentRun').findById(approval.run);
    const step = await mongoose.model('AgentRunStep').findById(approval.step).lean();
    const approvedStepIds = new Set(run.input?._approvedStepIds || []); approvedStepIds.add(step?.stepId);
    run.input = { ...(run.input || {}), _approvedStepIds: [...approvedStepIds] }; run.status = 'queued'; await run.save();
    await mongoose.model('Job').create({ tenant: req.tenantId, createdBy: req.admin?._id, type: 'agent.resume', payload: { run: approval.run, actor: req.admin?._id }, status: 'queued' });
  } else {
    await mongoose.model('AgentRun').findByIdAndUpdate(approval.run, { status: 'cancelled', completedAt: new Date() });
  }
  return json(res, approval, `Approval ${approval.status}`);
};

const approvals = async (req, res) => json(res, await mongoose.model('AgentApproval').find(scope(req)).sort({ created: -1 }).limit(50).lean(), 'Approvals loaded');

const assistant = async (req, res) => {
  const providers = await createProviderRegistryForTenant({ tenantId: req.tenantId });
  const result = await requestModel({ policy: req.body.modelPolicy || { provider: 'openai', fallbackProviders: ['kimi'] }, action: 'assistant', instruction: 'You are the in-house AI Agent Studio assistant. Recommend a safe agent purpose, instructions, tools, knowledge, and guardrails. Never publish or widen permissions.', prompt: req.body.message || JSON.stringify(req.body.context || {}), providers });
  return json(res, { message: result.content, provider: result.provider, usage: result.usage }, 'Assistant response generated');
};

const runDue = async (req, res) => json(res, await processDueAgentJobs({ limit: Number(req.body?.limit || 5) }), 'Due agent jobs processed');
const retry = async (req, res) => {
  const run = await mongoose.model('AgentRun').findOne({ _id: req.params.id, ...scope(req) });
  if (!run) return res.status(404).json({ success: false, message: 'Agent run not found' });
  run.status = 'queued';
  run.error = undefined;
  run.completedAt = undefined;
  await run.save();
  await mongoose.model('Job').create({ tenant: req.tenantId, createdBy: req.admin?._id, type: 'agent.resume', payload: { run: run._id, actor: req.admin?._id }, status: 'queued' });
  return json(res, run, 'Agent run retry queued');
};

module.exports = { templates, tools, createFromTemplate, publish, version, test: startRun(true), run: startRun(false), runDetail, runs, calendar, schedule, schedulePreview, retry, cancel, decideApproval, approvals, assistant, runDue };
