import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { logger } from "@/lib/logger";
import { callOpenRouter, callOpenRouterStream } from "@/lib/token/openrouter";
import { buildSystemPrompt } from "@/lib/token/system-prompt";
import { getTokenSpending, getCloudSpending, getSavingsEstimate, formatSpendingContext } from "@/lib/token/analytics";
import { checkAlerts, formatAlertContext } from "@/lib/token/alerts";
import { classifyIntent, selectModelForIntent, type TokenIntent } from "@/lib/token/classifier";
import { processAlertNotifications } from "@/lib/token/notifications";
import { incMessages, incCost, recordLatency } from "@/lib/token/metrics";
import { runResearch } from "@/lib/token/agents/researcher";
import { analyzeFleetRequest, type FleetActionProposal } from "@/lib/token/agents/fleet-manager";
import { planDeployment } from "@/lib/token/agents/deployment-planner";

/* ── Rate limiting (in-memory, per-process) ────────────────────────── */
const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
const RATE_LIMIT = 30;
const RATE_WINDOW_MS = 60_000;

function checkRateLimit(userId: string): boolean {
  const now = Date.now();
  const entry = rateLimitMap.get(userId);
  if (!entry || now > entry.resetAt) {
    rateLimitMap.set(userId, { count: 1, resetAt: now + RATE_WINDOW_MS });
    return true;
  }
  entry.count++;
  return entry.count <= RATE_LIMIT;
}

/**
 * POST /api/token/chat — Send a message to Token and get a response.
 *
 * Body: { message: string, conversationId?: string, stream?: boolean }
 * Returns (non-streaming): { reply, conversationId, model, inputTokens, outputTokens, costUsd, latencyMs }
 * Returns (streaming): SSE stream with data: {"token":"...","done":false} and final data: {"done":true,...}
 *
 * Auth: NextAuth session (dashboard users only).
 */
export async function POST(req: NextRequest) {
  const session = await auth();
  if (!session?.user?.id) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  // Rate limit: 30 messages/min per user
  if (!checkRateLimit(session.user.id)) {
    return NextResponse.json(
      { error: "Too many messages. Please wait a minute." },
      { status: 429 },
    );
  }

  try {
    const body = await req.json();
    const { message, conversationId, stream } = body;

    if (!message || typeof message !== "string" || message.trim().length === 0) {
      return NextResponse.json({ error: "message is required" }, { status: 400 });
    }

    if (message.length > 10_000) {
      return NextResponse.json(
        { error: "Message too long (max 10,000 characters)" },
        { status: 400 },
      );
    }

    // Load Token config (singleton row)
    let config = await db.tokenConfig.findUnique({ where: { id: "singleton" } });
    if (!config) {
      config = await db.tokenConfig.create({ data: { id: "singleton" } });
    }

    if (!config.enabled) {
      return NextResponse.json(
        { error: "Token assistant is currently disabled" },
        { status: 503 },
      );
    }

    // Budget check
    if (config.totalSpentUsd >= config.monthlyBudgetUsd) {
      return NextResponse.json(
        { error: "Monthly Token budget exceeded. Contact admin." },
        { status: 429 },
      );
    }

    // Load user info and fleet for system prompt
    const user = await db.user.findUnique({
      where: { id: session.user.id },
      select: { name: true, plan: true },
    });

    // Plan enforcement: Token requires Pro or higher
    if (!user || user.plan === "free") {
      return NextResponse.json(
        { error: "Token requires a Pro plan or higher. Upgrade at llmhosts.com/pricing." },
        { status: 403 },
      );
    }

    const devices = await db.device.findMany({
      where: { userId: session.user.id },
      orderBy: { lastSeen: "desc" },
    });

    // Load spending context for cost-aware responses (non-blocking — defaults on failure)
    let spendingContext: string | undefined;
    try {
      const [tokenSpending, cloudSpending, savings] = await Promise.all([
        getTokenSpending(session.user.id),
        getCloudSpending(session.user.id),
        getSavingsEstimate(session.user.id),
      ]);
      spendingContext = formatSpendingContext(tokenSpending, cloudSpending, savings);
    } catch (err) {
      logger.warn({ err, action: "token_chat_analytics_skip" }, "Skipping spending context");
    }

    // Load alert context for proactive fleet warnings (non-blocking — defaults on failure)
    let alertContext: string | undefined;
    try {
      const alerts = await checkAlerts(session.user.id);
      alertContext = formatAlertContext(alerts) || undefined;

      // Fire-and-forget: process email notifications in background
      if (alerts.length > 0 && session.user.email) {
        processAlertNotifications(
          session.user.id,
          session.user.email,
          session.user.name || "User",
          alerts,
        ).catch((err) => {
          logger.warn({ action: "token_notification_error", err }, "Failed to process notifications");
        });
      }
    } catch (err) {
      logger.warn({ err, action: "token_chat_alerts_skip" }, "Skipping alert context");
    }

    const systemPrompt = buildSystemPrompt({
      userName: user?.name ?? "Unknown",
      plan: user?.plan ?? "free",
      devices: devices.map((d) => ({
        name: d.name,
        status: d.status,
        models: JSON.parse(d.models) as string[],
        hardware: d.hardware,
        tunnelUrl: d.tunnelUrl,
      })),
      spendingContext,
      alertContext,
    });

    // Get or create conversation
    let conversation;
    if (conversationId) {
      conversation = await db.tokenConversation.findFirst({
        where: { id: conversationId, userId: session.user.id },
        include: { messages: { orderBy: { createdAt: "asc" }, take: 50 } },
      });
      if (!conversation) {
        return NextResponse.json({ error: "Conversation not found" }, { status: 404 });
      }
    } else {
      // Conversation limit: max 100 per user
      const convCount = await db.tokenConversation.count({ where: { userId: session.user.id } });
      if (convCount >= 100) {
        return NextResponse.json(
          { error: "Conversation limit reached (100). Delete old conversations to continue." },
          { status: 429 },
        );
      }

      // Generate a title from the first message
      const title = message.trim().slice(0, 80) + (message.length > 80 ? "..." : "");
      conversation = await db.tokenConversation.create({
        data: {
          userId: session.user.id,
          title,
        },
        include: { messages: true },
      });
    }

    // Save user message
    await db.tokenMessage.create({
      data: {
        conversationId: conversation.id,
        role: "user",
        content: message.trim(),
      },
    });

    // Build message history for LLM
    const history = conversation.messages.map((m) => ({
      role: m.role,
      content: m.content,
    }));
    history.push({ role: "user", content: message.trim() });

    // Add system prompt + custom prompt
    const messages = [
      { role: "system", content: systemPrompt + (config.systemPrompt ? `\n\n## Admin Instructions\n${config.systemPrompt}` : "") },
      ...history,
    ];

    // Classify intent to pick the right model
    let intent: TokenIntent = "chat";
    let selectedModel = config.chatModel;

    // Only classify if the message isn't too short (skip for greetings etc.)
    if (message.trim().length > 15) {
      const classification = await classifyIntent(message, config.classificationModel);
      intent = classification.intent;
      selectedModel = selectModelForIntent(intent, config);
    }

    // --- Research intent: use the dedicated research agent ---
    if (intent === "research") {
      const researchResult = await runResearch({
        query: message.trim(),
        model: selectedModel,
        fleetContext: systemPrompt,
        conversationHistory: history,
        maxTokens: config.maxTokensPerReply,
      });

      // Save assistant message
      await db.tokenMessage.create({
        data: {
          conversationId: conversation.id,
          role: "assistant",
          content: researchResult.content,
          model: researchResult.model,
          inputTokens: researchResult.inputTokens,
          outputTokens: researchResult.outputTokens,
          costUsd: researchResult.costUsd,
          latencyMs: researchResult.latencyMs,
          messageType: "research",
        },
      });

      await db.tokenConfig.update({
        where: { id: "singleton" },
        data: { totalSpentUsd: { increment: researchResult.costUsd } },
      });

      // Emit Prometheus metrics
      incMessages(researchResult.model, "research");
      incCost(researchResult.model, researchResult.costUsd);
      recordLatency(researchResult.model, researchResult.latencyMs);

      logger.info(
        {
          action: "token_chat_research",
          conversation_id: conversation.id,
          model: researchResult.model,
          cost_usd: researchResult.costUsd,
        },
        "Token research response via chat",
      );

      // If streaming was requested, wrap in SSE format
      if (stream) {
        const encoder = new TextEncoder();
        const convId = conversation.id;
        const researchStream = new ReadableStream<Uint8Array>({
          start(controller) {
            controller.enqueue(
              encoder.encode(`data: ${JSON.stringify({ conversationId: convId, done: false, intent })}\n\n`),
            );
            controller.enqueue(
              encoder.encode(`data: ${JSON.stringify({ token: researchResult.content, done: false })}\n\n`),
            );
            controller.enqueue(
              encoder.encode(
                `data: ${JSON.stringify({
                  done: true,
                  model: researchResult.model,
                  costUsd: researchResult.costUsd,
                  latencyMs: researchResult.latencyMs,
                  inputTokens: researchResult.inputTokens,
                  outputTokens: researchResult.outputTokens,
                  intent,
                })}\n\n`,
              ),
            );
            controller.close();
          },
        });

        return new Response(researchStream, {
          headers: {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            Connection: "keep-alive",
          },
        });
      }

      return NextResponse.json({
        reply: researchResult.content,
        conversationId: conversation.id,
        model: researchResult.model,
        inputTokens: researchResult.inputTokens,
        outputTokens: researchResult.outputTokens,
        costUsd: researchResult.costUsd,
        latencyMs: researchResult.latencyMs,
        intent,
      });
    }

    // --- Fleet action intent: use the fleet manager agent ---
    if (intent === "fleet_action") {
      const fleetDevices = devices.map((d) => ({
        id: d.id,
        name: d.name,
        status: d.status,
        models: JSON.parse(d.models) as string[],
        hardware: d.hardware,
        tunnelUrl: d.tunnelUrl,
      }));

      const fleetResult = await analyzeFleetRequest(
        message.trim(),
        selectedModel,
        fleetDevices,
        history,
      );

      // Save proposed actions to DB
      if (fleetResult.actions.length > 0) {
        await Promise.all(
          fleetResult.actions.map((a: FleetActionProposal) =>
            db.tokenFleetAction.create({
              data: {
                conversationId: conversation.id,
                deviceId: a.deviceId,
                type: a.type,
                payload: JSON.stringify(a.payload),
                status: "pending",
              },
            }),
          ),
        );
      }

      // Save assistant message
      await db.tokenMessage.create({
        data: {
          conversationId: conversation.id,
          role: "assistant",
          content: fleetResult.content,
          model: fleetResult.model,
          inputTokens: fleetResult.inputTokens,
          outputTokens: fleetResult.outputTokens,
          costUsd: fleetResult.costUsd,
          latencyMs: fleetResult.latencyMs,
          messageType: "fleet_action",
        },
      });

      await db.tokenConfig.update({
        where: { id: "singleton" },
        data: { totalSpentUsd: { increment: fleetResult.costUsd } },
      });

      // Emit Prometheus metrics
      incMessages(fleetResult.model, "fleet_action");
      incCost(fleetResult.model, fleetResult.costUsd);
      recordLatency(fleetResult.model, fleetResult.latencyMs);

      logger.info(
        {
          action: "token_chat_fleet",
          conversation_id: conversation.id,
          model: fleetResult.model,
          cost_usd: fleetResult.costUsd,
          actions_proposed: fleetResult.actions.length,
        },
        "Token fleet action response",
      );

      if (stream) {
        const encoder = new TextEncoder();
        const fleetStream = new ReadableStream<Uint8Array>({
          start(controller) {
            const convId = conversation.id;
            controller.enqueue(
              encoder.encode(
                `data: ${JSON.stringify({ conversationId: convId, done: false, intent })}\n\n`,
              ),
            );
            controller.enqueue(
              encoder.encode(
                `data: ${JSON.stringify({ token: fleetResult.content, done: false })}\n\n`,
              ),
            );
            // Include actions in final event so frontend can render action cards
            controller.enqueue(
              encoder.encode(
                `data: ${JSON.stringify({
                  done: true,
                  model: fleetResult.model,
                  costUsd: fleetResult.costUsd,
                  latencyMs: fleetResult.latencyMs,
                  inputTokens: fleetResult.inputTokens,
                  outputTokens: fleetResult.outputTokens,
                  intent,
                  actions: fleetResult.actions,
                })}\n\n`,
              ),
            );
            controller.close();
          },
        });

        return new Response(fleetStream, {
          headers: {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            Connection: "keep-alive",
          },
        });
      }

      return NextResponse.json({
        reply: fleetResult.content,
        conversationId: conversation.id,
        model: fleetResult.model,
        inputTokens: fleetResult.inputTokens,
        outputTokens: fleetResult.outputTokens,
        costUsd: fleetResult.costUsd,
        latencyMs: fleetResult.latencyMs,
        intent,
        actions: fleetResult.actions,
      });
    }

    // --- Analysis intent: use the deployment planner ---
    if (intent === "analysis") {
      const planDevices = devices.map((d) => ({
        id: d.id,
        name: d.name,
        status: d.status,
        models: JSON.parse(d.models) as string[],
        hardware: d.hardware,
        tunnelUrl: d.tunnelUrl,
      }));

      const planResult = await planDeployment(
        message.trim(),
        selectedModel,
        planDevices,
        history,
      );

      // Save proposed actions to DB
      if (planResult.actions.length > 0) {
        await Promise.all(
          planResult.actions.map((a) =>
            db.tokenFleetAction.create({
              data: {
                conversationId: conversation.id,
                deviceId: a.deviceId,
                type: a.type,
                payload: JSON.stringify(a.payload),
                status: "pending",
              },
            }),
          ),
        );
      }

      // Save assistant message
      await db.tokenMessage.create({
        data: {
          conversationId: conversation.id,
          role: "assistant",
          content: planResult.content,
          model: planResult.model,
          inputTokens: planResult.inputTokens,
          outputTokens: planResult.outputTokens,
          costUsd: planResult.costUsd,
          latencyMs: planResult.latencyMs,
          messageType: "deployment_plan",
        },
      });

      await db.tokenConfig.update({
        where: { id: "singleton" },
        data: { totalSpentUsd: { increment: planResult.costUsd } },
      });

      // Emit Prometheus metrics
      incMessages(planResult.model, "analysis");
      incCost(planResult.model, planResult.costUsd);
      recordLatency(planResult.model, planResult.latencyMs);

      logger.info(
        {
          action: "token_chat_deployment",
          conversation_id: conversation.id,
          model: planResult.model,
          cost_usd: planResult.costUsd,
          actions_proposed: planResult.actions.length,
        },
        "Token deployment plan response",
      );

      if (stream) {
        const encoder = new TextEncoder();
        const planStream = new ReadableStream<Uint8Array>({
          start(controller) {
            const convId = conversation.id;
            controller.enqueue(
              encoder.encode(
                `data: ${JSON.stringify({ conversationId: convId, done: false, intent })}\n\n`,
              ),
            );
            controller.enqueue(
              encoder.encode(
                `data: ${JSON.stringify({ token: planResult.content, done: false })}\n\n`,
              ),
            );
            controller.enqueue(
              encoder.encode(
                `data: ${JSON.stringify({
                  done: true,
                  model: planResult.model,
                  costUsd: planResult.costUsd,
                  latencyMs: planResult.latencyMs,
                  inputTokens: planResult.inputTokens,
                  outputTokens: planResult.outputTokens,
                  intent,
                  actions: planResult.actions,
                })}\n\n`,
              ),
            );
            controller.close();
          },
        });

        return new Response(planStream, {
          headers: {
            "Content-Type": "text/event-stream",
            "Cache-Control": "no-cache",
            Connection: "keep-alive",
          },
        });
      }

      return NextResponse.json({
        reply: planResult.content,
        conversationId: conversation.id,
        model: planResult.model,
        inputTokens: planResult.inputTokens,
        outputTokens: planResult.outputTokens,
        costUsd: planResult.costUsd,
        latencyMs: planResult.latencyMs,
        intent,
        actions: planResult.actions,
      });
    }

    // --- Streaming path ---
    if (stream) {
      const convId = conversation.id;
      const sseStream = await callOpenRouterStream(
        {
          messages,
          model: selectedModel,
          temperature: config.temperature,
          maxTokens: config.maxTokensPerReply,
        },
        async (payload) => {
          // Called after stream completes — persist assistant message and update spending
          await db.tokenMessage.create({
            data: {
              conversationId: convId,
              role: "assistant",
              content: payload.content,
              model: payload.model,
              inputTokens: payload.inputTokens,
              outputTokens: payload.outputTokens,
              costUsd: payload.costUsd,
              latencyMs: payload.latencyMs,
              messageType: intent,
            },
          });

          await db.tokenConfig.update({
            where: { id: "singleton" },
            data: { totalSpentUsd: { increment: payload.costUsd } },
          });

          // Emit Prometheus metrics
          incMessages(payload.model, intent);
          incCost(payload.model, payload.costUsd);
          recordLatency(payload.model, payload.latencyMs);

          logger.info(
            {
              action: "token_chat_stream",
              conversation_id: convId,
              model: payload.model,
              cost_usd: payload.costUsd,
            },
            "Token streaming chat response",
          );
        },
      );

      // Prepend conversationId as the first SSE event so the client learns it immediately
      const encoder = new TextEncoder();
      const prefixedStream = new ReadableStream<Uint8Array>({
        async start(controller) {
          // Emit conversation ID first so new conversations know their ID
          const initPayload = JSON.stringify({ conversationId: convId, done: false, intent });
          controller.enqueue(encoder.encode(`data: ${initPayload}\n\n`));

          // Pipe the rest of the SSE stream
          const reader = sseStream.getReader();
          try {
            while (true) {
              const { done, value } = await reader.read();
              if (done) break;
              controller.enqueue(value);
            }
            controller.close();
          } catch (err) {
            controller.error(err);
          }
        },
      });

      return new Response(prefixedStream, {
        headers: {
          "Content-Type": "text/event-stream",
          "Cache-Control": "no-cache",
          Connection: "keep-alive",
        },
      });
    }

    // --- Non-streaming path ---
    const llmResponse = await callOpenRouter({
      messages,
      model: selectedModel,
      temperature: config.temperature,
      maxTokens: config.maxTokensPerReply,
    });

    // Save assistant message
    await db.tokenMessage.create({
      data: {
        conversationId: conversation.id,
        role: "assistant",
        content: llmResponse.content,
        model: llmResponse.model,
        inputTokens: llmResponse.inputTokens,
        outputTokens: llmResponse.outputTokens,
        costUsd: llmResponse.costUsd,
        latencyMs: llmResponse.latencyMs,
        messageType: intent,
      },
    });

    // Update Token spending
    await db.tokenConfig.update({
      where: { id: "singleton" },
      data: { totalSpentUsd: { increment: llmResponse.costUsd } },
    });

    // Emit Prometheus metrics
    incMessages(llmResponse.model, intent);
    incCost(llmResponse.model, llmResponse.costUsd);
    recordLatency(llmResponse.model, llmResponse.latencyMs);

    logger.info(
      {
        action: "token_chat",
        conversation_id: conversation.id,
        model: llmResponse.model,
        cost_usd: llmResponse.costUsd,
      },
      "Token chat response",
    );

    return NextResponse.json({
      reply: llmResponse.content,
      conversationId: conversation.id,
      model: llmResponse.model,
      inputTokens: llmResponse.inputTokens,
      outputTokens: llmResponse.outputTokens,
      costUsd: llmResponse.costUsd,
      latencyMs: llmResponse.latencyMs,
      intent,
    });
  } catch (err) {
    logger.error({ err, action: "token_chat_error" }, "Token chat failed");
    return NextResponse.json(
      { error: "Token failed to respond. Please try again." },
      { status: 500 },
    );
  }
}
