import express, { Request, Response } from "express";
import twilio from "twilio";
import { storage } from "../storage";
import { prisma as db } from "@repo/db/client";
import { getHandoff, setHandoff, resetConversation, startNewPatientConversation, startRescheduleConversation, getAfterHoursHandoff, setAfterHoursHandoff } from "../ai/aiHandoffStore";

const router = express.Router();

function getTwilioClient(accountSid: string, authToken: string) {
  return twilio(accountSid, authToken);
}

// GET /api/twilio/settings
router.get("/settings", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const settings = await storage.getTwilioSettings(userId);
    if (!settings) return res.status(200).json(null);

    const templates = (settings.templates as Record<string, string>) || {};
    return res.status(200).json({
      id: settings.id,
      accountSid: settings.accountSid,
      authToken: settings.authToken,
      phoneNumber: settings.phoneNumber,
      greetingMessage: settings.greetingMessage,
      twimlAppSid: templates["_twiml_app_sid"] || null,
    });
  } catch (err) {
    return res.status(500).json({ error: "Failed to fetch Twilio settings", details: String(err) });
  }
});

// PUT /api/twilio/settings
router.put("/settings", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const { accountSid, authToken, phoneNumber, greetingMessage, twimlAppSid } = req.body;
    if (!accountSid?.trim() || !authToken?.trim() || !phoneNumber?.trim()) {
      return res.status(400).json({ message: "accountSid, authToken, and phoneNumber are required" });
    }

    const settings = await storage.upsertTwilioSettings(userId, {
      accountSid: accountSid.trim(),
      authToken: authToken.trim(),
      phoneNumber: phoneNumber.trim(),
      greetingMessage: greetingMessage?.trim() || null,
      twimlAppSid: twimlAppSid?.trim() || null,
    });

    const templates = (settings.templates as Record<string, string>) || {};
    return res.status(200).json({
      id: settings.id,
      accountSid: settings.accountSid,
      authToken: settings.authToken,
      phoneNumber: settings.phoneNumber,
      greetingMessage: settings.greetingMessage,
      twimlAppSid: templates["_twiml_app_sid"] || null,
    });
  } catch (err) {
    return res.status(500).json({ error: "Failed to save Twilio settings", details: String(err) });
  }
});

// POST /api/twilio/send-sms
router.post("/send-sms", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const { to, message, patientId, startFlow } = req.body;
    if (!to || !message) return res.status(400).json({ message: "to and message are required" });

    const settings = await storage.getTwilioSettings(userId);
    if (!settings) {
      return res.status(400).json({ message: "Twilio is not configured. Please add your Twilio credentials in Settings." });
    }

    const client = getTwilioClient(settings.accountSid, settings.authToken);
    const twilioMsg = await client.messages.create({
      body: message,
      from: settings.phoneNumber,
      to,
    });

    if (patientId) {
      const pid = Number(patientId);
      await storage.createCommunication({
        patientId: pid,
        userId,
        channel: "sms",
        direction: "outbound",
        status: "sent",
        body: message,
        twilioSid: twilioMsg.sid,
      });
      // Set conversation stage based on which flow was started
      if (startFlow === "new_patient") {
        await startNewPatientConversation(userId, pid);
      } else if (startFlow === "reschedule") {
        await startRescheduleConversation(userId, pid);
      } else if (startFlow === "no_follow_up") {
        // AI follow-up disabled — leave existing stage untouched
      } else {
        // "reminder" or unspecified → start reminder AI flow
        await resetConversation(userId, pid);
      }
    }

    return res.status(200).json({ sid: twilioMsg.sid, status: twilioMsg.status });
  } catch (err: any) {
    return res.status(500).json({ error: err.message || "Failed to send SMS" });
  }
});

// POST /api/twilio/send-reminders-batch
router.post("/send-reminders-batch", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const { date, staffIds, aiFollowUp = true } = req.body as {
      date: string;
      staffIds: number[];
      aiFollowUp?: boolean;
    };
    if (!date || !Array.isArray(staffIds) || staffIds.length === 0) {
      return res.status(400).json({ message: "date and staffIds are required" });
    }

    const settings = await storage.getTwilioSettings(userId);
    if (!settings) {
      return res.status(400).json({ message: "Twilio is not configured. Please add your Twilio credentials in Settings." });
    }

    // Resolve office name, address, phone, and reminder SMS template
    const officeContact = await storage.getOfficeContact(userId);
    const officeName = (officeContact as any)?.officeName?.trim() || "";
    const officeAddress = [
      (officeContact as any)?.streetAddress?.trim(),
      (officeContact as any)?.city?.trim(),
      (officeContact as any)?.state?.trim(),
      (officeContact as any)?.zipCode?.trim(),
    ].filter(Boolean).join(", ");
    const officePhone = (officeContact as any)?.phoneNumber?.trim() || "";
    const chatTemplates = await storage.getAiChatTemplates(userId);

    const DEFAULT_REMINDER_SMS =
      "Hi {firstName}, this is a reminder from {officeName}. You have an appointment on {appointmentDate} at {appointmentTime}. Please come to our office at {officeAddress}. Please reply YES to confirm or NO to reschedule. Thank you!";
    const templateBody = chatTemplates.reminderSms?.trim() || DEFAULT_REMINDER_SMS;

    // Fetch appointments for the selected staff columns on the given date
    const dayStart = new Date(date);
    dayStart.setUTCHours(0, 0, 0, 0);
    const dayEnd = new Date(date);
    dayEnd.setUTCHours(23, 59, 59, 999);

    const appointments = await db.appointment.findMany({
      where: {
        staffId: { in: staffIds },
        date: { gte: dayStart, lte: dayEnd },
        status: { not: "cancelled" },
        patient: { userId },
      },
      include: {
        patient: { select: { id: true, firstName: true, phone: true } },
      },
      orderBy: { startTime: "asc" },
    });

    // Format a date string as "May 9, 2026"
    const months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
    const formatApptDate = (d: Date | string) => {
      const dt = new Date(d);
      return `${months[dt.getUTCMonth()]} ${dt.getUTCDate()}, ${dt.getUTCFullYear()}`;
    };

    const client = getTwilioClient(settings.accountSid, settings.authToken);
    let sent = 0;
    let skipped = 0;

    // Deduplicate by patientId — send at most one reminder per patient per run
    const seen = new Set<number>();

    for (const appt of appointments) {
      const patient = appt.patient;
      if (!patient?.phone || seen.has(patient.id)) { skipped++; continue; }
      seen.add(patient.id);

      const apptDate      = formatApptDate(appt.date);
      const apptTime      = typeof appt.startTime === "string"
        ? appt.startTime.substring(0, 5)
        : String(appt.startTime);

      const message = templateBody
        .replace(/\{firstName\}/g,       patient.firstName ?? "")
        .replace(/\{officeName\}/g,      officeName)
        .replace(/\{officeAddress\}/g,   officeAddress)
        .replace(/\{officePhone\}/g,     officePhone)
        .replace(/\{twilioPhone\}/g,     settings.phoneNumber)
        .replace(/\{appointmentDate\}/g, apptDate)
        .replace(/\{appointmentTime\}/g, apptTime)
        .replace(/\{date\}/g,            apptDate)
        .replace(/\{time\}/g,            apptTime);

      try {
        const twilioMsg = await client.messages.create({
          body: message,
          from: settings.phoneNumber,
          to: patient.phone,
        });
        await storage.createCommunication({
          patientId: patient.id,
          userId,
          channel: "sms",
          direction: "outbound",
          status: "sent",
          body: message,
          twilioSid: twilioMsg.sid,
        });
        if (aiFollowUp) {
          await resetConversation(userId, patient.id);
        }
        sent++;
      } catch {
        skipped++;
      }
    }

    return res.status(200).json({ sent, skipped });
  } catch (err: any) {
    return res.status(500).json({ error: err.message || "Failed to send reminders" });
  }
});

// POST /api/twilio/send-reschedule-batch
router.post("/send-reschedule-batch", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const { date, staffIds, aiFollowUp = true } = req.body as { date: string; staffIds: number[]; aiFollowUp?: boolean };
    if (!date || !Array.isArray(staffIds) || staffIds.length === 0) {
      return res.status(400).json({ message: "date and staffIds are required" });
    }

    const settings = await storage.getTwilioSettings(userId);
    if (!settings) {
      return res.status(400).json({ message: "Twilio is not configured. Please add your Twilio credentials in Settings." });
    }

    // Find the "Reschedule by office" template from the SMS template list
    const templateList = await storage.getSmsTemplateList(userId);
    const rescheduleTemplate = templateList.find((t) =>
      t.name.toLowerCase().includes("reschedule") && t.name.toLowerCase().includes("office")
    ) || templateList.find((t) => t.name.toLowerCase().includes("reschedule"));

    const DEFAULT_RESCHEDULE_SMS =
      "Hi {firstName}, this is {officeName}. We need to reschedule your appointment. Please reply to let us know your availability. Thank you!";
    const templateBody = rescheduleTemplate?.body?.trim() || DEFAULT_RESCHEDULE_SMS;

    const officeContact = await storage.getOfficeContact(userId);
    const officeName = (officeContact as any)?.officeName?.trim() || "";
    const officeAddress = [
      (officeContact as any)?.streetAddress?.trim(),
      (officeContact as any)?.city?.trim(),
      (officeContact as any)?.state?.trim(),
      (officeContact as any)?.zipCode?.trim(),
    ].filter(Boolean).join(", ");
    const officePhone = (officeContact as any)?.phoneNumber?.trim() || "";

    const dayStart = new Date(date);
    dayStart.setUTCHours(0, 0, 0, 0);
    const dayEnd = new Date(date);
    dayEnd.setUTCHours(23, 59, 59, 999);

    const appointments = await db.appointment.findMany({
      where: {
        staffId: { in: staffIds },
        date: { gte: dayStart, lte: dayEnd },
        status: { not: "cancelled" },
        patient: { userId },
      },
      include: {
        patient: { select: { id: true, firstName: true, phone: true } },
      },
      orderBy: { startTime: "asc" },
    });

    const months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
    const formatApptDate = (d: Date | string) => {
      const dt = new Date(d);
      return `${months[dt.getUTCMonth()]} ${dt.getUTCDate()}, ${dt.getUTCFullYear()}`;
    };

    const client = getTwilioClient(settings.accountSid, settings.authToken);
    let sent = 0;
    let skipped = 0;
    const seen = new Set<number>();

    for (const appt of appointments) {
      const patient = appt.patient;
      if (!patient?.phone || seen.has(patient.id)) { skipped++; continue; }
      seen.add(patient.id);

      const apptDate = formatApptDate(appt.date);
      const apptTime = typeof appt.startTime === "string"
        ? appt.startTime.substring(0, 5)
        : String(appt.startTime);

      const message = templateBody
        .replace(/\{firstName\}/g,       patient.firstName ?? "")
        .replace(/\{officeName\}/g,      officeName)
        .replace(/\{officeAddress\}/g,   officeAddress)
        .replace(/\{officePhone\}/g,     officePhone)
        .replace(/\{twilioPhone\}/g,     settings.phoneNumber)
        .replace(/\{appointmentDate\}/g, apptDate)
        .replace(/\{appointmentTime\}/g, apptTime)
        .replace(/\{date\}/g,            apptDate)
        .replace(/\{time\}/g,            apptTime);

      try {
        const twilioMsg = await client.messages.create({
          body: message,
          from: settings.phoneNumber,
          to: patient.phone,
        });
        await storage.createCommunication({
          patientId: patient.id,
          userId,
          channel: "sms",
          direction: "outbound",
          status: "sent",
          body: message,
          twilioSid: twilioMsg.sid,
        });
        if (aiFollowUp) {
          await startRescheduleConversation(userId, patient.id);
        }
        sent++;
      } catch {
        skipped++;
      }
    }

    return res.status(200).json({ sent, skipped });
  } catch (err: any) {
    return res.status(500).json({ error: err.message || "Failed to send reschedule messages" });
  }
});

// GET /api/twilio/sms-template-list
router.get("/sms-template-list", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });
    const list = await storage.getSmsTemplateList(userId);
    return res.status(200).json(list);
  } catch (err) {
    return res.status(500).json({ error: "Failed to fetch SMS templates", details: String(err) });
  }
});

// PUT /api/twilio/sms-template-list
router.put("/sms-template-list", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });
    const templates = req.body;
    if (!Array.isArray(templates)) return res.status(400).json({ message: "Expected an array of templates" });
    await storage.saveSmsTemplateList(userId, templates);
    return res.status(200).json({ ok: true });
  } catch (err) {
    return res.status(500).json({ error: "Failed to save SMS templates", details: String(err) });
  }
});

// GET /api/twilio/templates
router.get("/templates", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });
    const templates = await storage.getTemplates(userId);
    return res.status(200).json(templates);
  } catch (err) {
    return res.status(500).json({ error: "Failed to fetch templates", details: String(err) });
  }
});

// PUT /api/twilio/templates/:key
router.put("/templates/:key", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });
    const { key } = req.params;
    const { body } = req.body;
    if (!key || !body?.trim()) return res.status(400).json({ message: "key and body are required" });
    await storage.saveTemplate(userId, key, body.trim());
    return res.status(200).json({ key, body: body.trim() });
  } catch (err) {
    return res.status(500).json({ error: "Failed to save template", details: String(err) });
  }
});

// GET /api/twilio/after-hours-handoff
router.get("/after-hours-handoff", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });
    return res.status(200).json({ enabled: await getAfterHoursHandoff(userId) });
  } catch (err) {
    return res.status(500).json({ error: "Failed to get after-hours handoff state" });
  }
});

// PUT /api/twilio/after-hours-handoff
router.put("/after-hours-handoff", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });
    const { enabled } = req.body;
    if (typeof enabled !== "boolean") return res.status(400).json({ message: "enabled must be a boolean" });
    await setAfterHoursHandoff(userId, enabled);
    return res.status(200).json({ enabled });
  } catch (err) {
    return res.status(500).json({ error: "Failed to set after-hours handoff state" });
  }
});

// GET /api/twilio/ai-handoff/:patientId
router.get("/ai-handoff/:patientId", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });
    const patientId = parseInt(req.params.patientId);
    if (isNaN(patientId)) return res.status(400).json({ message: "Invalid patientId" });
    return res.status(200).json({ enabled: await getHandoff(userId, patientId) });
  } catch (err) {
    return res.status(500).json({ error: "Failed to get AI handoff state" });
  }
});

// PUT /api/twilio/ai-handoff/:patientId
router.put("/ai-handoff/:patientId", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });
    const patientId = parseInt(req.params.patientId);
    if (isNaN(patientId)) return res.status(400).json({ message: "Invalid patientId" });
    const { enabled } = req.body;
    if (typeof enabled !== "boolean") return res.status(400).json({ message: "enabled must be a boolean" });
    await setHandoff(userId, patientId, enabled);
    return res.status(200).json({ enabled });
  } catch (err) {
    return res.status(500).json({ error: "Failed to set AI handoff state" });
  }
});

// GET /api/twilio/recent-communications
router.get("/recent-communications", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const limit = Math.min(50, parseInt(req.query.limit as string) || 20);
    const communications = await storage.getRecentCommunicationsByUser(userId, limit);
    return res.status(200).json(communications);
  } catch (err) {
    return res.status(500).json({ error: "Failed to fetch recent communications", details: String(err) });
  }
});

// POST /api/twilio/voice-token
// Returns a short-lived Twilio Access Token so the browser Voice SDK can place calls.
router.post("/voice-token", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const settings = await storage.getTwilioSettings(userId);
    if (!settings) {
      return res.status(400).json({ message: "Twilio is not configured. Please add your Twilio credentials in Settings." });
    }

    const templates = (settings.templates as Record<string, string>) || {};
    const twimlAppSid = templates["_twiml_app_sid"]?.trim();
    if (!twimlAppSid) {
      return res.status(400).json({ message: "TwiML App SID is not configured. Please add it in Twilio Settings." });
    }

    const AccessToken = twilio.jwt.AccessToken;
    const VoiceGrant = AccessToken.VoiceGrant;

    const token = new AccessToken(
      settings.accountSid,
      settings.accountSid,
      settings.authToken,
      { identity: `user-${userId}`, ttl: 3600 }
    );
    const voiceGrant = new VoiceGrant({ outgoingApplicationSid: twimlAppSid, incomingAllow: false });
    token.addGrant(voiceGrant);

    return res.status(200).json({ token: token.toJwt(), phoneNumber: settings.phoneNumber });
  } catch (err: any) {
    return res.status(500).json({ error: err.message || "Failed to generate voice token" });
  }
});

// POST /api/twilio/make-call
router.post("/make-call", async (req: Request, res: Response): Promise<any> => {
  try {
    const userId = req.user?.id;
    if (!userId) return res.status(401).json({ message: "Unauthorized" });

    const { to, message, patientId } = req.body;
    if (!to) return res.status(400).json({ message: "to is required" });

    const settings = await storage.getTwilioSettings(userId);
    if (!settings) {
      return res.status(400).json({ message: "Twilio is not configured. Please add your Twilio credentials in Settings." });
    }

    const twimlMessage = message || "Hello, this is a call from your dental office.";
    const twiml = `<Response><Say>${twimlMessage}</Say></Response>`;

    const client = getTwilioClient(settings.accountSid, settings.authToken);
    const call = await client.calls.create({
      twiml,
      from: settings.phoneNumber,
      to,
    });

    if (patientId) {
      await storage.createCommunication({
        patientId: Number(patientId),
        userId,
        channel: "voice",
        direction: "outbound",
        status: "queued",
        twilioSid: call.sid,
      });
    }

    return res.status(200).json({ sid: call.sid, status: call.status });
  } catch (err: any) {
    return res.status(500).json({ error: err.message || "Failed to make call" });
  }
});

export default router;
