import type { FastifyInstance } from 'fastify';
import type { CalendarEventType, CalendarRequestStatus } from '@wa-ticketing/shared';
import { sql } from '../db.js';
import { authenticate, requireRole } from '../middleware/auth.js';
import {
  calendarService,
  type BookOverrides,
  type ClientConfirmStatus,
} from '../services/calendarService.js';
import { canStaffSendOnChannel } from '../services/channelProviderRules.js';
import { channelService } from '../services/channelService.js';
import { contactService } from '../services/contactService.js';
import { emitMessageNew } from '../services/messageEmitService.js';
import { messagingService } from '../services/messagingService.js';
import { enrichMessageWithMediaUrl } from '../services/ticketMediaService.js';
import { ticketService } from '../services/ticketService.js';

const staffOnly = [authenticate, requireRole('admin', 'user')];

function parseStatuses(raw: unknown): CalendarRequestStatus[] | undefined {
  if (raw == null || raw === '') return undefined;
  const parts = String(raw).split(',').map(s => s.trim()).filter(Boolean);
  const allowed: CalendarRequestStatus[] = ['suggested', 'booked', 'declined', 'cancelled'];
  const out = parts.filter((p): p is CalendarRequestStatus =>
    (allowed as string[]).includes(p),
  );
  return out.length ? out : undefined;
}

function defaultConfirmMessage(summary: string, startsAt: string): string {
  const when = new Date(startsAt);
  const stamp = Number.isNaN(when.getTime())
    ? startsAt
    : when.toLocaleString('en-ZA', { dateStyle: 'medium', timeStyle: 'short' });
  return `Hi — we'd like to confirm your booking${summary ? ` (${summary})` : ''} for ${stamp}. Reply YES to confirm or NO if you need to reschedule.`;
}

export async function calendarRoutes(fastify: FastifyInstance) {
  // Staff picker for assignee field
  fastify.get('/staff', { preHandler: staffOnly }, async (_req, reply) => {
    return reply.send(await calendarService.listStaffAssignees());
  });

  fastify.get('/requests', { preHandler: staffOnly }, async (req, reply) => {
    const q = req.query as {
      status?: string;
      assigned_to?: string;
      from?: string;
      to?: string;
      ticket_id?: string;
      limit?: string;
    };
    const rows = await calendarService.listRequests({
      status: parseStatuses(q.status),
      assignedTo: q.assigned_to,
      from: q.from,
      to: q.to,
      ticketId: q.ticket_id,
      limit: q.limit ? Number(q.limit) : 100,
    });
    return reply.send(rows);
  });

  fastify.get('/events', { preHandler: staffOnly }, async (req, reply) => {
    const q = req.query as { from?: string; to?: string; assigned_to?: string };
    if (!q.from || !q.to) {
      return reply.status(400).send({ error: 'from and to query params are required (ISO datetimes)' });
    }
    const rows = await calendarService.listBookedEvents({
      from: q.from,
      to: q.to,
      assignedTo: q.assigned_to,
    });
    return reply.send(rows);
  });

  fastify.get('/availability', { preHandler: staffOnly }, async (req, reply) => {
    const q = req.query as {
      assigned_to?: string;
      starts_at?: string;
      ends_at?: string;
      buffer_before_minutes?: string;
      buffer_after_minutes?: string;
      exclude_id?: string;
    };
    if (!q.assigned_to || !q.starts_at || !q.ends_at) {
      return reply.status(400).send({ error: 'assigned_to, starts_at, and ends_at are required' });
    }
    try {
      const result = await calendarService.checkAvailability({
        assignedTo: q.assigned_to,
        startsAt: q.starts_at,
        endsAt: q.ends_at,
        bufferBeforeMinutes: q.buffer_before_minutes != null ? Number(q.buffer_before_minutes) : 30,
        bufferAfterMinutes: q.buffer_after_minutes != null ? Number(q.buffer_after_minutes) : 30,
        excludeId: q.exclude_id,
      });
      return reply.send(result);
    } catch (e: unknown) {
      const err = e as { statusCode?: number; message?: string };
      return reply.status(err.statusCode ?? 500).send({ error: err.message ?? 'Error' });
    }
  });

  /** Create a booked appointment from the day grid (contact + channel + time). */
  fastify.post('/requests', { preHandler: staffOnly }, async (req, reply) => {
    const body = req.body as {
      contact_id?: string;
      phone_number?: string;
      channel_id?: string;
      event_type?: CalendarEventType;
      summary?: string;
      starts_at?: string;
      ends_at?: string;
      assigned_to?: string;
      buffer_before_minutes?: number;
      buffer_after_minutes?: number;
      await_client_confirm?: boolean;
      confirm_message?: string;
    };
    if (!body.channel_id || !body.starts_at || !body.ends_at || !body.assigned_to) {
      return reply.status(400).send({
        error: 'channel_id, starts_at, ends_at, and assigned_to are required',
      });
    }
    const channel = await channelService.getById(body.channel_id);
    if (!channel || channel.kind !== 'whatsapp') {
      return reply.status(400).send({ error: 'channel_id must be a WhatsApp channel' });
    }
    if (!canStaffSendOnChannel(channel)) {
      return reply.status(400).send({ error: 'Channel is not connected' });
    }
    try {
      const created = await calendarService.createBooked({
        contactId: body.contact_id,
        phoneNumber: body.phone_number,
        channelId: body.channel_id,
        eventType: body.event_type === 'meetup' ? 'meetup' : 'call_out',
        summary: body.summary?.trim() || 'Appointment',
        startsAt: body.starts_at,
        endsAt: body.ends_at,
        assignedTo: body.assigned_to,
        actorId: req.user!.sub,
        bufferBeforeMinutes: body.buffer_before_minutes,
        bufferAfterMinutes: body.buffer_after_minutes,
        awaitClientConfirm: Boolean(body.await_client_confirm),
      });
      fastify.io.emit('calendar:updated', { id: created.id, status: created.status });

      if (body.await_client_confirm) {
        const contact = await contactService.getById(created.contact_id);
        const ticket = await ticketService.getById(created.ticket_id);
        if (contact && ticket && contactService.isWhatsAppPhone(contact.phone_number)) {
          const text = (body.confirm_message?.trim()
            || defaultConfirmMessage(created.summary, created.starts_at));
          const message = await messagingService.appendMessage(
            created.ticket_id, 'outbound', text, null, req.user!.sub,
          );
          try {
            const waMessageId = await channelService.sendMessage(
              created.channel_id, contact.phone_number, text,
            );
            if (waMessageId) {
              await sql`UPDATE messages SET wa_message_id = ${waMessageId} WHERE id = ${message.id}`;
              message.wa_message_id = waMessageId;
            }
            await emitMessageNew(fastify.io, created.ticket_id, await enrichMessageWithMediaUrl(message));
            await calendarService.setClientConfirm(created.id, req.user!.sub, 'pending', created.channel_id);
          } catch (err) {
            // Booking stays; confirmation send failure is reported
            return reply.status(201).send({
              ...created,
              confirm_send_error: String(err),
            });
          }
        }
      }

      return reply.status(201).send(await calendarService.getById(created.id));
    } catch (e: unknown) {
      const err = e as { statusCode?: number; message?: string; conflicts?: unknown };
      if (err.statusCode === 409) {
        return reply.status(409).send({ error: err.message, conflicts: err.conflicts ?? [] });
      }
      return reply.status(err.statusCode ?? 500).send({ error: err.message ?? 'Error' });
    }
  });

  fastify.get('/requests/:id', { preHandler: staffOnly }, async (req, reply) => {
    const { id } = req.params as { id: string };
    const row = await calendarService.getById(id);
    if (!row) return reply.status(404).send({ error: 'Calendar request not found' });
    return reply.send(row);
  });

  /** WhatsApp the client to confirm a booked appointment (pick channel). */
  fastify.post('/requests/:id/confirm-message', { preHandler: staffOnly }, async (req, reply) => {
    const { id } = req.params as { id: string };
    const body = req.body as { channel_id?: string; content?: string };
    const row = await calendarService.getById(id);
    if (!row) return reply.status(404).send({ error: 'Calendar request not found' });
    if (row.status !== 'booked') {
      return reply.status(400).send({ error: 'Only booked events can request client confirmation' });
    }

    const channelId = body.channel_id?.trim() || row.channel_id;
    const channel = await channelService.getById(channelId);
    if (!channel || channel.kind !== 'whatsapp') {
      return reply.status(400).send({ error: 'Select a connected WhatsApp channel' });
    }
    if (!canStaffSendOnChannel(channel)) {
      return reply.status(400).send({ error: 'Channel is not connected' });
    }

    const contact = await contactService.getById(row.contact_id);
    if (!contact || !contactService.isWhatsAppPhone(contact.phone_number)) {
      return reply.status(400).send({ error: 'Contact has no WhatsApp number' });
    }

    // Keep messaging on the booking's ticket; switch channel only for outbound send target rules
    const text = body.content?.trim() || defaultConfirmMessage(row.summary, row.starts_at);
    const message = await messagingService.appendMessage(
      row.ticket_id, 'outbound', text, null, req.user!.sub,
    );
    try {
      const waMessageId = await channelService.sendMessage(channelId, contact.phone_number, text);
      if (waMessageId) {
        await sql`UPDATE messages SET wa_message_id = ${waMessageId} WHERE id = ${message.id}`;
        message.wa_message_id = waMessageId;
      }
      await emitMessageNew(fastify.io, row.ticket_id, await enrichMessageWithMediaUrl(message));
      const updated = await calendarService.setClientConfirm(id, req.user!.sub, 'pending', channelId);
      fastify.io.emit('calendar:updated', { id, status: updated.status });
      return reply.send({ request: updated, message: await enrichMessageWithMediaUrl(message) });
    } catch (err) {
      return reply.status(502).send({ error: `WhatsApp send failed: ${String(err)}` });
    }
  });

  /** Staff marks client YES/NO after they reply (or cancel wait). */
  fastify.post('/requests/:id/client-confirm', { preHandler: staffOnly }, async (req, reply) => {
    const { id } = req.params as { id: string };
    const body = req.body as { status?: ClientConfirmStatus };
    if (body.status !== 'confirmed' && body.status !== 'declined' && body.status !== 'pending') {
      return reply.status(400).send({ error: 'status must be pending, confirmed, or declined' });
    }
    try {
      const updated = await calendarService.setClientConfirm(id, req.user!.sub, body.status);
      fastify.io.emit('calendar:updated', { id, status: updated.status });
      return reply.send(updated);
    } catch (e: unknown) {
      const err = e as { statusCode?: number; message?: string };
      return reply.status(err.statusCode ?? 500).send({ error: err.message ?? 'Error' });
    }
  });

  fastify.patch('/requests/:id', { preHandler: staffOnly }, async (req, reply) => {
    const { id } = req.params as { id: string };
    const body = req.body as BookOverrides;
    try {
      const updated = await calendarService.updateDraft(id, body);
      return reply.send(updated);
    } catch (e: unknown) {
      const err = e as { statusCode?: number; message?: string };
      return reply.status(err.statusCode ?? 500).send({ error: err.message ?? 'Error' });
    }
  });

  fastify.post('/requests/:id/book', { preHandler: staffOnly }, async (req, reply) => {
    const { id } = req.params as { id: string };
    const body = (req.body ?? {}) as BookOverrides;
    try {
      const booked = await calendarService.book(id, req.user!.sub, body);
      fastify.io.emit('calendar:updated', { id: booked.id, status: booked.status });
      return reply.send(booked);
    } catch (e: unknown) {
      const err = e as { statusCode?: number; message?: string; conflicts?: unknown };
      if (err.statusCode === 409) {
        return reply.status(409).send({ error: err.message, conflicts: err.conflicts ?? [] });
      }
      return reply.status(err.statusCode ?? 500).send({ error: err.message ?? 'Error' });
    }
  });

  fastify.post('/requests/:id/decline', { preHandler: staffOnly }, async (req, reply) => {
    const { id } = req.params as { id: string };
    try {
      const row = await calendarService.decline(id, req.user!.sub);
      fastify.io.emit('calendar:updated', { id: row.id, status: row.status });
      return reply.send(row);
    } catch (e: unknown) {
      const err = e as { statusCode?: number; message?: string };
      return reply.status(err.statusCode ?? 500).send({ error: err.message ?? 'Error' });
    }
  });

  fastify.post('/requests/:id/cancel', { preHandler: staffOnly }, async (req, reply) => {
    const { id } = req.params as { id: string };
    try {
      const row = await calendarService.cancel(id, req.user!.sub);
      fastify.io.emit('calendar:updated', { id: row.id, status: row.status });
      return reply.send(row);
    } catch (e: unknown) {
      const err = e as { statusCode?: number; message?: string };
      return reply.status(err.statusCode ?? 500).send({ error: err.message ?? 'Error' });
    }
  });
}
