/**
 * Token deployment planner agent.
 *
 * Uses the reasoning model (DeepSeek R1 via OpenRouter) for complex
 * multi-device model placement optimization. Considers VRAM constraints,
 * model capabilities, redundancy, and use case mix.
 */

import { logger } from "@/lib/logger";
import type { FleetActionProposal, FleetDevice } from "./fleet-manager";

const OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions";

const PLANNER_SYSTEM_PROMPT = `You are Token's Deployment Planner — an expert at optimizing LLM model placement across multiple devices with different hardware.

## Your Task
Given a fleet of devices with varying VRAM and current model deployments, create an optimal deployment plan that maximizes capability coverage while respecting hardware constraints.

## VRAM Reference
- qwen3:1.7b: 1.4 GB (ultra-light, general)
- qwen3:4b: 2.5 GB (light, general)
- qwen3:8b: 5.2 GB (mid, general)
- qwen3:14b: 9.3 GB (strong, general)
- qwen3:32b: 20 GB (premium, general)
- llama3.3:70b: 40 GB (top-tier, general)
- qwen2.5-coder:7b: 5 GB (coding)
- qwen2.5-coder:14b: 9 GB (coding, strong)
- deepseek-r1:7b: 5 GB (reasoning)
- deepseek-r1:14b: 9 GB (reasoning)
- llama3.2-vision:11b: 8 GB (vision/multimodal)
- nomic-embed-text: 0.3 GB (embeddings)

## Optimization Criteria (Priority Order)
1. **VRAM feasibility**: Never exceed device VRAM. Leave ~1-2 GB headroom for OS/runtime.
2. **Capability coverage**: Ensure the fleet covers: general chat, coding, reasoning, vision (if possible).
3. **Redundancy**: For critical models, deploy on 2+ devices if hardware allows.
4. **Efficiency**: Don't waste VRAM on duplicate models unless for redundancy.
5. **Use case fit**: Match model strengths to typical user needs.

## Output Format
Respond with a JSON object:
{
  "summary": "1-2 sentence overview of the plan",
  "plan": [
    {
      "deviceId": "<id>",
      "deviceName": "<name>",
      "currentModels": ["model1", "model2"],
      "proposedModels": ["model1", "model3", "model4"],
      "toInstall": ["model3", "model4"],
      "toRemove": ["model2"],
      "vramUsed": "12.5 GB",
      "vramTotal": "16 GB",
      "rationale": "Why these changes"
    }
  ],
  "capabilityCoverage": {
    "general": true,
    "coding": true,
    "reasoning": true,
    "vision": false,
    "embeddings": true
  },
  "estimatedActions": <number of install/remove operations>
}

IMPORTANT: Return ONLY the JSON object, no markdown wrapping.`;

export interface DeploymentPlanEntry {
  deviceId: string;
  deviceName: string;
  currentModels: string[];
  proposedModels: string[];
  toInstall: string[];
  toRemove: string[];
  vramUsed: string;
  vramTotal: string;
  rationale: string;
}

export interface DeploymentPlanResult {
  summary: string;
  plan: DeploymentPlanEntry[];
  capabilityCoverage: Record<string, boolean>;
  estimatedActions: number;
  actions: FleetActionProposal[];
  content: string;
  model: string;
  inputTokens: number;
  outputTokens: number;
  costUsd: number;
  latencyMs: number;
}

export async function planDeployment(
  message: string,
  model: string,
  devices: FleetDevice[],
  conversationHistory?: Array<{ role: string; content: string }>,
): Promise<DeploymentPlanResult> {
  const apiKey = process.env.OPENROUTER_API_KEY;
  if (!apiKey) {
    throw new Error("OPENROUTER_API_KEY not configured");
  }

  const start = Date.now();

  const fleetState = devices
    .map((d) => {
      const hw = d.hardware || "unknown hardware";
      return `- ${d.name} (${d.id}): status=${d.status}, hardware=${hw}, models=[${d.models.join(", ")}]`;
    })
    .join("\n");

  const messages = [
    {
      role: "system",
      content: PLANNER_SYSTEM_PROMPT + "\n\n## Current Fleet\n" + fleetState,
    },
    ...(conversationHistory ?? []),
    { role: "user", content: message },
  ];

  const res = await fetch(OPENROUTER_URL, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
      "HTTP-Referer": "https://llmhosts.com",
      "X-Title": "LLMHosts Token Deployment Planner",
    },
    body: JSON.stringify({
      model,
      messages,
      temperature: 0.2,
      max_tokens: 4096,
    }),
  });

  if (!res.ok) {
    const errorText = await res.text().catch(() => "Unknown error");
    logger.error(
      { action: "token_planner_error", status: res.status },
      "Deployment planner API error",
    );
    throw new Error(`Deployment planner API error (${res.status}): ${errorText}`);
  }

  const data = await res.json();
  const latencyMs = Date.now() - start;
  const rawContent = data.choices?.[0]?.message?.content ?? "";
  const usage = data.usage ?? {};

  const inputTokens = usage.prompt_tokens ?? 0;
  const outputTokens = usage.completion_tokens ?? 0;
  const costUsd = parseFloat(res.headers.get("x-openrouter-cost") ?? "0");

  let summary = "";
  let plan: DeploymentPlanEntry[] = [];
  let capabilityCoverage: Record<string, boolean> = {};
  let estimatedActions = 0;

  try {
    const jsonMatch = rawContent.match(/\{[\s\S]*\}/);
    if (jsonMatch) {
      const parsed = JSON.parse(jsonMatch[0]);
      summary = parsed.summary ?? "";
      plan = parsed.plan ?? [];
      capabilityCoverage = parsed.capabilityCoverage ?? {};
      estimatedActions = parsed.estimatedActions ?? 0;
    }
  } catch (err) {
    logger.warn(
      { action: "token_planner_parse_fail", err },
      "Failed to parse deployment plan",
    );
    summary = rawContent;
  }

  // Convert plan entries to fleet action proposals
  const actions: FleetActionProposal[] = [];
  for (const entry of plan) {
    for (const modelName of entry.toInstall ?? []) {
      actions.push({
        deviceId: entry.deviceId,
        deviceName: entry.deviceName,
        type: "pull_model",
        payload: { model: modelName },
        reasoning: entry.rationale,
      });
    }
    for (const modelName of entry.toRemove ?? []) {
      actions.push({
        deviceId: entry.deviceId,
        deviceName: entry.deviceName,
        type: "remove_model",
        payload: { model: modelName },
        reasoning: entry.rationale,
      });
    }
  }

  // Build human-readable markdown content
  let content = `## Deployment Plan\n\n${summary}\n\n`;

  if (plan.length > 0) {
    content +=
      "| Device | Current | Proposed | VRAM |\n|--------|---------|----------|------|\n";
    for (const entry of plan) {
      content += `| ${entry.deviceName} | ${entry.currentModels.join(", ") || "none"} | ${entry.proposedModels.join(", ") || "none"} | ${entry.vramUsed} / ${entry.vramTotal} |\n`;
    }

    content += "\n### Changes\n";
    for (const entry of plan) {
      if (
        (entry.toInstall?.length ?? 0) > 0 ||
        (entry.toRemove?.length ?? 0) > 0
      ) {
        content += `\n**${entry.deviceName}**:`;
        for (const m of entry.toInstall ?? []) {
          content += `\n- Install \`${m}\``;
        }
        for (const m of entry.toRemove ?? []) {
          content += `\n- Remove \`${m}\``;
        }
        content += `\n- _${entry.rationale}_`;
      }
    }

    const covered = Object.entries(capabilityCoverage)
      .filter(([, v]) => v)
      .map(([k]) => k);
    const missing = Object.entries(capabilityCoverage)
      .filter(([, v]) => !v)
      .map(([k]) => k);

    content += "\n\n### Coverage\n";
    if (covered.length > 0) content += `Covered: ${covered.join(", ")}\n`;
    if (missing.length > 0)
      content += `Not covered: ${missing.join(", ")} (insufficient hardware)\n`;

    content += `\n**${estimatedActions} actions** to execute this plan. Approve to proceed.`;
  }

  logger.info(
    {
      action: "token_deployment_plan",
      model,
      devices_count: devices.length,
      actions_count: actions.length,
      latency_ms: latencyMs,
      cost_usd: costUsd,
    },
    "Deployment plan complete",
  );

  return {
    summary,
    plan,
    capabilityCoverage,
    estimatedActions,
    actions,
    content,
    model: data.model ?? model,
    inputTokens,
    outputTokens,
    costUsd,
    latencyMs,
  };
}
