/**
 * Voice Agent Provisioning API
 *
 * POST /api/voice-agent/provision
 * Creates a new ElevenLabs conversation agent for the authenticated user
 */

import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";
import { logger } from "@/lib/logger";
import { elevenLabsService } from "@/lib/services/elevenlabs.service";
import { elevenLabsServerToolsService } from "@/lib/services/elevenlabs-server-tools.service";
import { elevenLabsPhoneService } from "@/lib/services/elevenlabs-phone.service";
import { voiceAgentKBSyncService } from "@/lib/services/voice-agent-kb-sync.service";
import { prisma } from "@/lib/db";
import { provisionAgentSchema } from "@/lib/validators/voice-agent.validator";
import { VoiceAgentConfig } from "@/lib/types/voice-agent.types";
import { z } from "zod";
import { withSecureRoute } from "@/lib/api-security";
import {
  applyRateLimit,
  getClientIdentifier,
  rateLimiters,
} from "@/lib/rate-limiter";
import { getAuthenticatedUser } from "@/lib/utils/auth-helpers.util";
import { createHash, randomBytes } from "crypto";
import { getDataSource } from "@/lib/data-source";

function getBaseUrl(): string {
  const envUrl =
    process.env.NEXT_PUBLIC_APP_URL ||
    process.env.NEXT_PUBLIC_BASE_URL ||
    "http://localhost:9000";
  return envUrl.endsWith("/") ? envUrl.slice(0, -1) : envUrl;
}

const WEBHOOK_FALLBACK_URL = getBaseUrl();

export const POST = withSecureRoute(
  async (request: NextRequest, _context, body: unknown) => {
    try {
      if (!body) {
        return NextResponse.json(
          { error: "Request body is required" },
          { status: 400 },
        );
      }

      const authenticatedUser = await getAuthenticatedUser(request, auth);
      if (!authenticatedUser) {
        return NextResponse.json(
          {
            error: "Unauthorized",
            message: "You must be logged in to provision a voice agent",
          },
          { status: 401 },
        );
      }

      // ── Convex data-source branch ──────────────────────────────────────
      if (getDataSource("voice_agents") === "convex") {
        try {
          const { convexQuery, convexMutation } =
            await import("@/lib/convex-client");

          const cvxUser = (await convexQuery(
            "voice_agents/queries:getUserVoiceAgent",
            {
              userId: authenticatedUser.userId,
            },
          )) as {
            id: string;
            voiceAgentId: string | null;
            voiceAgentConfig: unknown;
          } | null;

          if (!cvxUser) {
            return NextResponse.json(
              { error: "User not found" },
              { status: 404 },
            );
          }

          const rateLimitId = cvxUser.id
            ? `user:${cvxUser.id}`
            : getClientIdentifier(request);
          const rateLimited = await applyRateLimit(
            request,
            rateLimiters.voiceProvision,
            rateLimitId,
          );
          if (rateLimited) return rateLimited;

          if (cvxUser.voiceAgentId) {
            return NextResponse.json(
              {
                error: "Agent already exists",
                message:
                  "You already have a voice agent provisioned. Use the update endpoint to modify it.",
                agentId: cvxUser.voiceAgentId,
              },
              { status: 409 },
            );
          }

          const validatedConfig = provisionAgentSchema.parse(body);

          logger.info("Provisioning voice agent (Convex)", {
            userId: cvxUser.id,
            config: validatedConfig,
          });

          const derivedIdempotencyKey = createHash("sha256")
            .update(`${cvxUser.id}:${JSON.stringify(validatedConfig)}`)
            .digest("hex");
          const idempotencyKey =
            request.headers.get("idempotency-key") ||
            `voice-provision-${derivedIdempotencyKey}`;
          const webhookToken = randomBytes(24).toString("hex");

          // External ElevenLabs API calls — stay as-is
          const agent = await elevenLabsService.createConversationAgent(
            validatedConfig as VoiceAgentConfig,
            cvxUser.id,
            undefined,
            { idempotencyKey },
          );
          const config = validatedConfig as VoiceAgentConfig;
          const tools = elevenLabsServerToolsService.buildFilteredBookingTools(
            webhookToken,
            config.enabledTools,
          );
          const toolsResult = await elevenLabsServerToolsService.registerTools(
            agent.agent_id,
            tools,
          );
          const toolsConfigured = toolsResult.success;
          if (!toolsResult.success) {
            logger.error(
              "Failed to register server tools after provisioning — agent saved without tools",
              new Error(
                toolsResult.error || "Unknown tools registration error",
              ),
              { userId: cvxUser.id, agentId: agent.agent_id },
            );
          }

          // Persist to Convex
          await convexMutation("voice_agents/mutations:provisionAgent", {
            userId: cvxUser.id,
            voiceAgentId: agent.agent_id,
            voiceAgentConfig: validatedConfig,
            n8nWebhookToken: webhookToken,
          });

          let kbSyncResult:
            | { synced: number; failed: number; errors: string[] }
            | undefined;
          if (validatedConfig.useKnowledgeBase) {
            kbSyncResult = await voiceAgentKBSyncService.syncAllDocuments(
              cvxUser.id,
              agent.agent_id,
            );
          }

          // Phone/SIP config — external + Convex DB
          let phoneResult:
            | { success: boolean; sipEndpoint?: string; error?: string }
            | undefined;
          if (
            validatedConfig.phoneConfig &&
            validatedConfig.phoneConfig.connectionType !== "none"
          ) {
            const pc = validatedConfig.phoneConfig;
            if (pc.connectionType === "custom_sip" && pc.sipUri) {
              phoneResult = await elevenLabsPhoneService.configurePhone(
                agent.agent_id,
                {
                  connectionType: "custom_sip",
                  isActive: true,
                  sip: { sipUri: pc.sipUri },
                },
              );
              if (phoneResult.success) {
                await convexMutation(
                  "voice_agents/mutations:createPhoneConfig",
                  {
                    userId: cvxUser.id,
                    connectionType: "custom_sip" as const,
                    sipUri: pc.sipUri,
                    phoneNumber: pc.phoneNumber || undefined,
                    isActive: true,
                  },
                );
              }
            } else if (
              pc.connectionType === "twilio_native" &&
              pc.phoneNumber
            ) {
              await convexMutation("voice_agents/mutations:createPhoneConfig", {
                userId: cvxUser.id,
                connectionType: "twilio_native" as const,
                phoneNumber: pc.phoneNumber,
                isActive: false,
              });
              phoneResult = { success: true };
            }
          }

          const webhookUrl = `${WEBHOOK_FALLBACK_URL}/api/voice-agent/webhook`;

          logger.info("Successfully provisioned voice agent (Convex)", {
            userId: cvxUser.id,
            agentId: agent.agent_id,
          });

          return NextResponse.json(
            {
              success: true,
              message: "Voice agent provisioned successfully",
              data: {
                agentId: agent.agent_id,
                webhookUrl,
                config: validatedConfig,
                toolsEnabled: toolsConfigured,
                toolsWarning: toolsConfigured
                  ? undefined
                  : `Booking tools could not be registered and will need to be configured via voice agent settings. Reason: ${toolsResult.error ?? "unknown"}`,
                knowledgeBaseSync: kbSyncResult
                  ? { synced: kbSyncResult.synced, failed: kbSyncResult.failed }
                  : undefined,
                phoneConfig: phoneResult
                  ? {
                      configured: phoneResult.success,
                      sipEndpoint: phoneResult.sipEndpoint,
                      error: phoneResult.error,
                    }
                  : undefined,
                warnings:
                  kbSyncResult && kbSyncResult.failed > 0
                    ? kbSyncResult.errors
                    : undefined,
                widgetCode: `<elevenlabs-convai agent-id="${agent.agent_id}"></elevenlabs-convai>`,
              },
            },
            { status: 201 },
          );
        } catch (convexError) {
          if (convexError instanceof z.ZodError) throw convexError;
          if (
            convexError instanceof Error &&
            convexError.name === "ElevenLabsServiceError"
          )
            throw convexError;
          console.error(
            "[Convex fallback] voice_agents provision:",
            convexError,
          );
          // Fall through to Prisma path
        }
      }

      // ── Prisma path (default) ─────────────────────────────────────────
      const user = await prisma.users.findUnique({
        where: { id: authenticatedUser.userId },
        select: {
          id: true,
          email: true,
          name: true,
          voiceAgentId: true,
          voiceAgentConfig: true,
          n8nWebhookToken: true,
        },
      });

      if (!user) {
        return NextResponse.json({ error: "User not found" }, { status: 404 });
      }

      const rateLimitId = user.id
        ? `user:${user.id}`
        : getClientIdentifier(request);
      const rateLimited = await applyRateLimit(
        request,
        rateLimiters.voiceProvision,
        rateLimitId,
      );
      if (rateLimited) {
        return rateLimited;
      }

      if (user.voiceAgentId) {
        return NextResponse.json(
          {
            error: "Agent already exists",
            message:
              "You already have a voice agent provisioned. Use the update endpoint to modify it.",
            agentId: user.voiceAgentId,
          },
          { status: 409 },
        );
      }

      const validatedConfig = provisionAgentSchema.parse(body);

      logger.info("Provisioning voice agent", {
        userId: user.id,
        config: validatedConfig,
      });

      const derivedIdempotencyKey = createHash("sha256")
        .update(`${user.id}:${JSON.stringify(validatedConfig)}`)
        .digest("hex");
      const idempotencyKey =
        request.headers.get("idempotency-key") ||
        `voice-provision-${derivedIdempotencyKey}`;
      const webhookToken =
        user.n8nWebhookToken || randomBytes(24).toString("hex");
      const createdWebhookToken = !user.n8nWebhookToken;

      const agent = await elevenLabsService.createConversationAgent(
        validatedConfig as VoiceAgentConfig,
        user.id,
        user.name || undefined,
        { idempotencyKey },
      );
      const config = validatedConfig as VoiceAgentConfig;
      const tools = elevenLabsServerToolsService.buildFilteredBookingTools(
        webhookToken,
        config.enabledTools,
      );
      const toolsResult = await elevenLabsServerToolsService.registerTools(
        agent.agent_id,
        tools,
      );
      const toolsConfigured = toolsResult.success;
      if (!toolsResult.success) {
        logger.error(
          "Failed to register server tools after provisioning — agent saved without tools",
          new Error(toolsResult.error || "Unknown tools registration error"),
          {
            userId: user.id,
            agentId: agent.agent_id,
          },
        );
      }

      const userUpdateData: Record<string, unknown> = {
        voiceAgentId: agent.agent_id,
        voiceAgentConfig: validatedConfig as unknown,
        updatedAt: new Date(),
      };
      if (createdWebhookToken) {
        userUpdateData.n8nWebhookToken = webhookToken;
      }
      await prisma.users.update({
        where: { id: user.id },
        data: userUpdateData,
      });

      let kbSyncResult:
        | { synced: number; failed: number; errors: string[] }
        | undefined;
      if (validatedConfig.useKnowledgeBase) {
        kbSyncResult = await voiceAgentKBSyncService.syncAllDocuments(
          user.id,
          agent.agent_id,
        );
      }

      // Configure phone/SIP if provided
      let phoneResult:
        | { success: boolean; sipEndpoint?: string; error?: string }
        | undefined;
      if (
        validatedConfig.phoneConfig &&
        validatedConfig.phoneConfig.connectionType !== "none"
      ) {
        const pc = validatedConfig.phoneConfig;
        if (pc.connectionType === "custom_sip" && pc.sipUri) {
          phoneResult = await elevenLabsPhoneService.configurePhone(
            agent.agent_id,
            {
              connectionType: "custom_sip",
              isActive: true,
              sip: { sipUri: pc.sipUri },
            },
          );
          if (phoneResult.success) {
            await prisma.voiceAgentPhoneConfig.upsert({
              where: { userId: user.id },
              create: {
                userId: user.id,
                connectionType: "custom_sip",
                sipUri: pc.sipUri,
                phoneNumber: pc.phoneNumber || null,
                isActive: true,
              },
              update: {
                connectionType: "custom_sip",
                sipUri: pc.sipUri,
                phoneNumber: pc.phoneNumber || null,
                isActive: true,
              },
            });
          }
        } else if (pc.connectionType === "twilio_native" && pc.phoneNumber) {
          // Store phone number for Twilio - full Twilio config requires credentials via settings
          await prisma.voiceAgentPhoneConfig.upsert({
            where: { userId: user.id },
            create: {
              userId: user.id,
              connectionType: "twilio_native",
              phoneNumber: pc.phoneNumber,
              isActive: false, // requires Twilio credentials to activate
            },
            update: {
              connectionType: "twilio_native",
              phoneNumber: pc.phoneNumber,
              isActive: false,
            },
          });
          phoneResult = { success: true };
        }
      }

      const webhookUrl = `${WEBHOOK_FALLBACK_URL}/api/voice-agent/webhook`;

      logger.info("Successfully provisioned voice agent", {
        userId: user.id,
        agentId: agent.agent_id,
      });

      return NextResponse.json(
        {
          success: true,
          message: "Voice agent provisioned successfully",
          data: {
            agentId: agent.agent_id,
            webhookUrl,
            config: validatedConfig,
            toolsEnabled: toolsConfigured,
            toolsWarning: toolsConfigured
              ? undefined
              : `Booking tools could not be registered and will need to be configured via voice agent settings. Reason: ${toolsResult.error ?? "unknown"}`,
            knowledgeBaseSync: kbSyncResult
              ? {
                  synced: kbSyncResult.synced,
                  failed: kbSyncResult.failed,
                }
              : undefined,
            phoneConfig: phoneResult
              ? {
                  configured: phoneResult.success,
                  sipEndpoint: phoneResult.sipEndpoint,
                  error: phoneResult.error,
                }
              : undefined,
            warnings:
              kbSyncResult && kbSyncResult.failed > 0
                ? kbSyncResult.errors
                : undefined,
            widgetCode: `<elevenlabs-convai agent-id="${agent.agent_id}"></elevenlabs-convai>`,
          },
        },
        { status: 201 },
      );
    } catch (error) {
      if (error instanceof z.ZodError) {
        return NextResponse.json(
          {
            error: "Validation error",
            message: "Invalid configuration provided",
            details: error.errors,
          },
          { status: 400 },
        );
      }

      if (error instanceof Error && error.name === "ElevenLabsServiceError") {
        const serviceError = error as {
          statusCode?: number;
          details?: Record<string, unknown>;
          message: string;
        };
        logger.error("ElevenLabs service error during provisioning", error);

        const isAuthError =
          serviceError.statusCode === 401 || serviceError.statusCode === 403;
        return NextResponse.json(
          {
            error: "elevenlabs_error",
            errorType:
              (serviceError.details?.errorType as string) ||
              (isAuthError ? "permission_error" : "service_error"),
            message: serviceError.message,
            details: serviceError.details || undefined,
          },
          { status: isAuthError ? 502 : 500 },
        );
      }

      logger.error("Error provisioning voice agent", error);
      return NextResponse.json(
        {
          error: "Internal server error",
          message: "Failed to provision voice agent",
        },
        { status: 500 },
      );
    }
  },
);

/**
 * GET /api/voice-agent/provision
 * Get provisioning status and information
 */
export const GET = withSecureRoute(
  async (request: NextRequest) => {
    try {
      const authenticatedUser = await getAuthenticatedUser(request, auth);
      if (!authenticatedUser) {
        return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
      }

      // ── Convex data-source branch ──────────────────────────────────────
      if (getDataSource("voice_agents") === "convex") {
        try {
          const { convexQuery } = await import("@/lib/convex-client");

          const cvxUser = (await convexQuery(
            "voice_agents/queries:getUserVoiceAgent",
            {
              userId: authenticatedUser.userId,
            },
          )) as {
            id: string;
            voiceAgentId: string | null;
            voiceAgentConfig: unknown;
          } | null;

          if (!cvxUser) {
            return NextResponse.json(
              { error: "User not found" },
              { status: 404 },
            );
          }

          const rateLimitId = cvxUser.id
            ? `user:${cvxUser.id}`
            : getClientIdentifier(request);
          const rateLimited = await applyRateLimit(
            request,
            rateLimiters.voiceRead,
            rateLimitId,
          );
          if (rateLimited) return rateLimited;

          if (!cvxUser.voiceAgentId) {
            return NextResponse.json(
              {
                provisioned: false,
                message: "No voice agent provisioned for this user",
              },
              { status: 200 },
            );
          }

          const status = await elevenLabsService.getAgentStatus(
            cvxUser.voiceAgentId,
          );

          return NextResponse.json(
            {
              provisioned: true,
              agentId: cvxUser.voiceAgentId,
              config: cvxUser.voiceAgentConfig,
              status,
              webhookUrl: `${WEBHOOK_FALLBACK_URL}/api/voice-agent/webhook`,
            },
            { status: 200 },
          );
        } catch (convexError) {
          console.error(
            "[Convex fallback] voice_agents provision GET:",
            convexError,
          );
        }
      }

      // ── Prisma path (default) ─────────────────────────────────────────
      const user = await prisma.users.findUnique({
        where: { id: authenticatedUser.userId },
        select: {
          id: true,
          voiceAgentId: true,
          voiceAgentConfig: true,
        },
      });

      if (!user) {
        return NextResponse.json({ error: "User not found" }, { status: 404 });
      }

      const rateLimitId = user.id
        ? `user:${user.id}`
        : getClientIdentifier(request);
      const rateLimited = await applyRateLimit(
        request,
        rateLimiters.voiceRead,
        rateLimitId,
      );
      if (rateLimited) {
        return rateLimited;
      }

      if (!user.voiceAgentId) {
        return NextResponse.json(
          {
            provisioned: false,
            message: "No voice agent provisioned for this user",
          },
          { status: 200 },
        );
      }

      const status = await elevenLabsService.getAgentStatus(user.voiceAgentId);

      return NextResponse.json(
        {
          provisioned: true,
          agentId: user.voiceAgentId,
          config: user.voiceAgentConfig,
          status,
          webhookUrl: `${WEBHOOK_FALLBACK_URL}/api/voice-agent/webhook`,
        },
        { status: 200 },
      );
    } catch (error) {
      logger.error("Error fetching provisioning status", error);
      return NextResponse.json(
        {
          error: "Internal server error",
          message: "Failed to fetch provisioning status",
        },
        { status: 500 },
      );
    }
  },
  { parseJson: false },
);
