import express from 'express';
import crypto from 'crypto';
import jwt from 'jsonwebtoken';
import { userDb, subscriptionDb, voiceCallDb } from '../database/db.js';

const router = express.Router();

const VAPI_PRIVATE_KEY = process.env.VAPI_PRIVATE_KEY;
const VAPI_PUBLIC_KEY = process.env.VAPI_PUBLIC_KEY;
const VAPI_ASSISTANT_ID = process.env.VAPI_ASSISTANT_ID;
const JWT_SECRET = process.env.JWT_SECRET?.trim();

// ─── Session Context Store ──────────────────────────────────────────────────
// Reliable user identification for VAPI webhook callbacks.
// Problem: VAPI doesn't reliably propagate assistantOverrides.metadata back to
// the webhook for tool-calls (especially for Chat API). So we store user context
// server-side keyed by a short-lived sessionId, and pass that sessionId in metadata.
// The webhook reads sessionId from metadata and looks up the full user context here.

const sessionStore = new Map();
const SESSION_TTL_MS = 60 * 60 * 1000; // 1 hour

function createSession(userCtx) {
  const sessionId = `vs_${crypto.randomBytes(12).toString('hex')}`;
  sessionStore.set(sessionId, { userCtx, createdAt: Date.now() });
  return sessionId;
}

function getSessionContext(sessionId) {
  if (!sessionId) return null;
  const entry = sessionStore.get(sessionId);
  if (!entry) return null;
  if (Date.now() - entry.createdAt > SESSION_TTL_MS) {
    sessionStore.delete(sessionId);
    return null;
  }
  return entry.userCtx;
}

// Cleanup expired sessions every 15 minutes
setInterval(() => {
  const now = Date.now();
  for (const [key, entry] of sessionStore) {
    if (now - entry.createdAt > SESSION_TTL_MS) sessionStore.delete(key);
  }
}, 15 * 60 * 1000).unref();

// ─── Helpers ────────────────────────────────────────────────────────────────

function friendlyPlan(planId) {
  if (!planId) return 'free';
  const p = planId.toLowerCase();
  if (p.includes('month')) return 'Monthly';
  if (p.includes('half') || p.includes('6')) return 'Half-Yearly';
  if (p.includes('year') || p.includes('annual')) return 'Annual';
  return planId;
}

function daysBetween(a, b) {
  return Math.ceil((new Date(b) - new Date(a)) / 86400000);
}

async function tryGetUser(req) {
  try {
    let token = req.cookies?.session;
    if (!token) {
      const auth = req.headers['authorization'];
      if (auth?.startsWith('Bearer ')) token = auth.slice(7);
    }
    if (!token || !JWT_SECRET) return null;

    const decoded = jwt.verify(token, JWT_SECRET);
    const user = await userDb.getUserById(decoded.userId);
    if (!user) return null;

    const sub = await subscriptionDb.getActiveSub(user.id);

    const fullName = [user.first_name, user.last_name].filter(Boolean).join(' ') || user.username || 'AI Enthusiast';
    const accountAgeDays = daysBetween(user.created_at, new Date());

    const ctx = {
      userId: String(user.id),
      userName: user.first_name || user.username || 'AI Enthusiast',
      fullName,
      userEmail: user.email || null,
      phone: user.phone || null,
      accountAgeDays,
      memberSince: user.created_at ? new Date(user.created_at).toLocaleDateString('en-IN', { month: 'long', year: 'numeric' }) : null,
    };

    if (sub) {
      const daysLeft = daysBetween(new Date(), sub.expires_at);
      ctx.userPlan = friendlyPlan(sub.plan_id);
      ctx.planStatus = sub.status;
      ctx.planExpiresAt = sub.expires_at;
      ctx.planDaysLeft = daysLeft;
      ctx.isPro = true;
    } else {
      ctx.userPlan = 'free';
      ctx.isPro = false;
    }

    return ctx;
  } catch {
    return null;
  }
}

const GUEST_CONTEXT = { userName: 'AI Enthusiast', userPlan: 'not logged in' };

// ─── Metadata extraction: check every possible path VAPI might use ──────────

function extractMetadata(message) {
  // VAPI places metadata in different locations depending on voice vs chat
  return message.call?.assistantOverrides?.metadata
    || message.call?.metadata
    || message.chat?.assistantOverrides?.metadata
    || message.chat?.metadata
    || message.metadata
    || {};
}

// Resolve user context from metadata — tries session store first, then userId fallback
async function resolveUserFromMetadata(metadata) {
  // Priority 1: Session store (most reliable — we stored context server-side)
  if (metadata.sessionId) {
    const ctx = getSessionContext(metadata.sessionId);
    if (ctx) return ctx;
  }

  // Priority 2: Direct userId in metadata (fallback if session expired)
  if (metadata.userId) {
    return { userId: metadata.userId, ...metadata };
  }

  return null;
}

// ─── POST /api/vapi/webhook — VAPI Server URL handler ──────────────────────
// VAPI calls this during conversations for tool-calls.
// Tools handled:
//   - getUserContext: fetches user name/plan/email
//   - troubleshootConnection: checks CLI relay status
//   - getSubscriptionDetails: detailed subscription info
//   - getPricingInfo: current pricing plans
//   - getFeatureComparison: free vs pro features
//   - endCall: ends the current call

router.post('/webhook', async (req, res) => {
  const { message } = req.body;
  if (!message?.type) {
    return res.status(400).json({ error: 'Invalid VAPI server event' });
  }

  switch (message.type) {
    case 'tool-calls': {
      const metadata = extractMetadata(message);

      const results = await Promise.all(
        (message.toolCallList || []).map(async (toolCall) => {
          const fn = toolCall.function;

          if (fn.name === 'getUserContext') {
            return {
              toolCallId: toolCall.id,
              result: JSON.stringify(await handleGetUserContext(metadata)),
            };
          }

          if (fn.name === 'troubleshootConnection') {
            return {
              toolCallId: toolCall.id,
              result: JSON.stringify(await handleTroubleshootConnection(metadata)),
            };
          }

          if (fn.name === 'getSubscriptionDetails') {
            return {
              toolCallId: toolCall.id,
              result: JSON.stringify(await handleGetSubscriptionDetails(metadata)),
            };
          }

          if (fn.name === 'getPricingInfo') {
            return {
              toolCallId: toolCall.id,
              result: JSON.stringify(handleGetPricingInfo()),
            };
          }

          if (fn.name === 'getFeatureComparison') {
            return {
              toolCallId: toolCall.id,
              result: JSON.stringify(handleGetFeatureComparison()),
            };
          }

          if (fn.name === 'endCall') {
            return {
              toolCallId: toolCall.id,
              result: JSON.stringify({ action: 'end_call', message: 'Call ended by assistant.' }),
            };
          }

          return { toolCallId: toolCall.id, result: '{}' };
        })
      );
      return res.json({ results });
    }

    case 'assistant-request': {
      if (VAPI_ASSISTANT_ID) {
        return res.json({ assistantId: VAPI_ASSISTANT_ID });
      }
      return res.json({ error: 'No assistant configured' });
    }

    case 'end-of-call-report': {
      // Persist call data to Turso, then clean up session
      const metadata = extractMetadata(message);
      const userCtx = metadata.sessionId ? getSessionContext(metadata.sessionId) : null;
      const userId = userCtx?.userId || metadata.userId;

      if (userId) {
        try {
          const call = message.call || message;
          const vapiCallId = call.id || call.callId || null;

          // Build transcript from messages array
          const msgs = message.artifact?.messages || call.messages || [];
          const transcriptLines = msgs
            .filter(m => m.role && m.message)
            .map(m => `${m.role}: ${m.message}`)
            .join('\n');

          // Collect tools used
          const toolsUsed = msgs
            .filter(m => m.role === 'tool_calls' || m.toolCalls)
            .flatMap(m => (m.toolCalls || []).map(t => t.function?.name))
            .filter(Boolean);

          await voiceCallDb.complete(userId, vapiCallId, {
            status: call.status || 'completed',
            endedReason: call.endedReason || message.endedReason || null,
            durationSeconds: call.duration ? Math.round(call.duration) : (message.durationSeconds || null),
            cost: call.cost || message.cost || null,
            transcript: transcriptLines || null,
            summary: message.artifact?.summary || message.summary || null,
            toolsUsed: toolsUsed.length > 0 ? JSON.stringify(toolsUsed) : null,
            messagesCount: msgs.length || 0,
          });
        } catch { /* Best-effort persistence — don't block webhook response */ }
      }

      if (metadata.sessionId) sessionStore.delete(metadata.sessionId);
      return res.json({});
    }

    case 'status-update': {
      // Track call start — create initial record in Turso
      const metadata2 = extractMetadata(message);
      const userCtx2 = metadata2.sessionId ? getSessionContext(metadata2.sessionId) : null;
      const userId2 = userCtx2?.userId || metadata2.userId;
      const callStatus = message.status || message.call?.status;

      if (userId2 && callStatus === 'in-progress') {
        try {
          const vapiCallId = message.call?.id || null;
          const callId = await voiceCallDb.create(userId2, 'voice', metadata2.sessionId || null);
          if (vapiCallId) await voiceCallDb.setVapiCallId(callId, vapiCallId);
        } catch { /* Best-effort */ }
      }
      return res.json({});
    }

    case 'conversation-update':
    case 'transcript':
    case 'speech-update':
    case 'hang':
      return res.json({});

    default:
      return res.json({});
  }
});

// ─── Tool handlers ──────────────────────────────────────────────────────────

async function handleGetUserContext(metadata) {
  try {
    const resolved = await resolveUserFromMetadata(metadata);

    if (!resolved || !resolved.userId) {
      return {
        name: 'AI Enthusiast',
        plan: 'not logged in',
        isPro: false,
        note: 'User is not signed in — treat them warmly as a curious visitor and encourage sign-up.',
      };
    }

    const userId = resolved.userId;
    const user = await userDb.getUserById(userId);
    if (!user) {
      return { name: 'AI Enthusiast', plan: 'not logged in', isPro: false, note: 'User not found.' };
    }

    const sub = await subscriptionDb.getActiveSub(user.id);
    const fullName = [user.first_name, user.last_name].filter(Boolean).join(' ') || user.username;
    const accountAgeDays = daysBetween(user.created_at, new Date());

    const ctx = {
      name: user.first_name || user.username,
      fullName,
      email: user.email || null,
      phone: user.phone || null,
      accountAgeDays,
      memberSince: user.created_at ? new Date(user.created_at).toLocaleDateString('en-IN', { month: 'long', year: 'numeric' }) : null,
    };

    if (sub) {
      const daysLeft = daysBetween(new Date(), sub.expires_at);
      ctx.plan = friendlyPlan(sub.plan_id);
      ctx.planStatus = sub.status;
      ctx.planExpiresAt = sub.expires_at;
      ctx.planDaysLeft = daysLeft;
      ctx.isPro = true;
      ctx.note = daysLeft <= 7
        ? `Subscription expires in ${daysLeft} days — gently remind them to renew.`
        : `Active ${friendlyPlan(sub.plan_id)} subscriber.`;
    } else {
      ctx.plan = 'free';
      ctx.isPro = false;
      ctx.note = 'Free tier user — if relevant, mention the benefits of upgrading.';
    }

    return ctx;
  } catch {
    return { name: 'AI Enthusiast', plan: 'unknown', isPro: false, note: 'Lookup failed.' };
  }
}

async function handleGetSubscriptionDetails(metadata) {
  try {
    const resolved = await resolveUserFromMetadata(metadata);
    if (!resolved?.userId) {
      return {
        hasSubscription: false,
        message: 'User is not logged in. They need to sign in to view subscription details.',
        suggestion: 'Encourage the user to sign up or log in at cli.upfyn.com',
      };
    }

    const sub = await subscriptionDb.getActiveSub(resolved.userId);
    if (!sub) {
      return {
        hasSubscription: false,
        currentPlan: 'free',
        message: 'User is on the free tier (BYOK — Bring Your Own Key). No active paid subscription.',
        suggestion: 'Mention the Pro plan benefits if they ask about upgrading.',
      };
    }

    const daysLeft = daysBetween(new Date(), sub.expires_at);
    return {
      hasSubscription: true,
      currentPlan: friendlyPlan(sub.plan_id),
      status: sub.status,
      expiresAt: sub.expires_at,
      daysRemaining: daysLeft,
      autoRenew: sub.status === 'active',
      message: daysLeft <= 7
        ? `Subscription expires in ${daysLeft} days. Remind them to renew.`
        : `Active ${friendlyPlan(sub.plan_id)} subscription with ${daysLeft} days remaining.`,
    };
  } catch {
    return { hasSubscription: false, message: 'Could not fetch subscription details.' };
  }
}

function handleGetPricingInfo() {
  return {
    currency: 'INR',
    plans: [
      {
        name: 'Free',
        price: 0,
        billing: 'forever',
        features: [
          'Bring Your Own API Key (BYOK)',
          'Connect to Claude Code, Codex, Cursor',
          'Web-based IDE with terminal & file explorer',
          'AI chat with your own API key',
          'Basic canvas/whiteboard',
        ],
        bestFor: 'Developers who already have API keys and want a visual interface',
      },
      {
        name: 'Monthly Pro',
        price: 499,
        billing: 'per month',
        features: [
          'Everything in Free',
          'Server-provided AI (no API key needed)',
          'Priority support',
          'Advanced canvas features',
          'Voice assistant (Upfyn)',
          'Unlimited projects',
        ],
        bestFor: 'Developers who want a hassle-free experience',
      },
      {
        name: '6-Month Pro',
        price: 2499,
        billing: 'every 6 months',
        savingsVsMonthly: '17% savings vs monthly',
        features: ['Everything in Monthly Pro', 'Lower per-month cost'],
        bestFor: 'Committed users who want to save',
      },
      {
        name: 'Annual Pro',
        price: 4999,
        billing: 'per year',
        savingsVsMonthly: '17% savings vs monthly',
        currentOffer: 'Launch offer: first year at just ₹499!',
        features: ['Everything in Monthly Pro', 'Best value', 'Launch discount available'],
        bestFor: 'Best deal — especially with the launch offer',
      },
    ],
    note: 'All prices in INR. Pro plans include server-provided AI so users don\'t need their own API keys.',
  };
}

function handleGetFeatureComparison() {
  return {
    comparison: {
      'Web IDE (terminal, files, git)': { free: true, pro: true },
      'Connect to Claude Code / Codex / Cursor': { free: true, pro: true },
      'BYOK AI Chat (your own API key)': { free: true, pro: true },
      'Canvas / Whiteboard': { free: 'basic', pro: 'advanced' },
      'Server-provided AI (no API key needed)': { free: false, pro: true },
      'Voice Assistant (Upfyn)': { free: false, pro: true },
      'Priority Support': { free: false, pro: true },
      'Multiple simultaneous projects': { free: 'limited', pro: 'unlimited' },
      'MCP Integration (Claude Desktop, ChatGPT, Cursor)': { free: true, pro: true },
    },
    summary: 'Free tier is great for developers who have their own API keys. Pro adds server-provided AI, voice assistant, and premium features.',
    upgradeUrl: 'https://cli.upfyn.com/pricing',
  };
}

async function handleTroubleshootConnection(metadata) {
  try {
    const resolved = await resolveUserFromMetadata(metadata);
    if (!resolved?.userId) {
      return {
        connected: false,
        message: 'User is not logged in. They need to sign in first, then run "uc connect" on their machine.',
      };
    }

    const userId = resolved.userId;
    const port = process.env.PORT || 3001;
    const baseUrl = `http://localhost:${port}`;

    const token = jwt.sign({ userId: Number(userId) }, JWT_SECRET, { expiresIn: '1m' });

    const res = await fetch(`${baseUrl}/api/relay/status`, {
      headers: { 'Authorization': `Bearer ${token}` },
    });

    if (!res.ok) {
      return { connected: false, message: 'Could not check connection status.' };
    }

    const data = await res.json();

    if (data.connected) {
      return {
        connected: true,
        message: 'Machine is connected and online! Everything looks good.',
        local: data.local || false,
        connectedAt: data.connectedAt || null,
      };
    }

    return {
      connected: false,
      message: 'Machine is NOT connected. Tell the user to open a terminal and run: uc connect',
      troubleshooting: [
        'Make sure upfynai-code is installed: npm install -g upfynai-code',
        'Run: uc connect',
        'Check if their firewall is blocking WebSocket connections',
        'Try: uc doctor — to diagnose issues',
      ],
    };
  } catch {
    return { connected: false, message: 'Could not check connection status.' };
  }
}

// ─── POST /api/vapi/chat — Proxy to VAPI Chat API ──────────────────────────
// Body: { message?, chatId? }
// Returns: { reply, chatId }

router.post('/chat', async (req, res) => {
  if (!VAPI_PRIVATE_KEY || !VAPI_ASSISTANT_ID) {
    return res.status(503).json({ error: 'Chat not configured' });
  }

  const { message, chatId } = req.body;

  try {
    const userCtx = await tryGetUser(req);
    const vars = userCtx || GUEST_CONTEXT;

    // Create session so webhook can identify this user
    const sessionId = userCtx ? createSession(userCtx) : null;

    const metadata = userCtx
      ? { ...userCtx, sessionId }
      : {};

    const body = {
      assistantId: VAPI_ASSISTANT_ID,
      assistantOverrides: {
        metadata,
        variableValues: { userName: vars.userName },
      },
    };

    if (message && typeof message === 'string') {
      body.input = message.slice(0, 2000);
    } else {
      body.input = `Greet me briefly. My name is ${vars.userName}.`;
    }
    if (chatId) body.previousChatId = chatId;

    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 30000);

    const response = await fetch('https://api.vapi.ai/chat', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${VAPI_PRIVATE_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
      signal: controller.signal,
    });

    clearTimeout(timeout);

    if (!response.ok) {
      const err = await response.text().catch(() => '');
      return res.status(response.status).json({ error: 'VAPI chat request failed', details: err });
    }

    const data = await response.json();

    const assistantMsg = [...(data.output || [])].reverse().find(o => o.role === 'assistant' && o.content);
    const reply = assistantMsg?.content || 'No response';

    const responseChatId = data.chat?.id || chatId || null;

    // Persist chat call to Turso
    const chatUserCtx = await tryGetUser(req);
    if (chatUserCtx?.userId) {
      try {
        await voiceCallDb.saveChat(chatUserCtx.userId, sessionId, responseChatId, 1);
      } catch { /* Best-effort */ }
    }

    res.json({
      reply,
      chatId: responseChatId,
    });
  } catch (error) {
    if (error.name === 'AbortError') {
      return res.status(504).json({ error: 'Chat request timed out' });
    }
    res.status(500).json({ error: 'Chat request failed' });
  }
});

// ─── POST /api/assistant/call-context — Return assistantOverrides for VAPI SDK
// Frontend calls this to get user-specific overrides, then starts call via VAPI Web SDK
router.post('/call-context', async (req, res) => {
  try {
    const userCtx = await tryGetUser(req);
    const vars = userCtx || GUEST_CONTEXT;

    // Create session so webhook can identify this user during the voice call
    const sessionId = userCtx ? createSession(userCtx) : null;

    let greeting;
    if (userCtx) {
      const name = vars.userName;
      if (vars.accountAgeDays <= 1) {
        greeting = `Welcome to Upfyn, ${name}! Great to have you on board. How can I help you get started?`;
      } else if (vars.isPro) {
        greeting = `Hey ${name}! What can I do for you today?`;
      } else {
        greeting = `Hey ${name}! What can I help you with?`;
      }
    } else {
      greeting = 'Hey there! Welcome to Upfyn. What can I help you with?';
    }

    res.json({
      metadata: userCtx ? { ...userCtx, sessionId } : {},
      variableValues: { userName: vars.userName },
      firstMessage: greeting,
    });
  } catch {
    res.json({
      metadata: {},
      variableValues: { userName: GUEST_CONTEXT.userName },
      firstMessage: 'Hey there! Welcome to Upfyn. What can I help you with?',
    });
  }
});

export default router;
