import type { FastifyInstance } from 'fastify';
import { authenticate, requireRole } from '../middleware/auth.js';
import { sql } from '../db.js';
import { ticketService } from '../services/ticketService.js';
import { messagingService } from '../services/messagingService.js';
import { channelService } from '../services/channelService.js';
import { auditService } from '../services/auditService.js';
import { userCanAccessTicket } from '../services/ticketAccessService.js';
import { isPortalChannelPhone } from '@wa-ticketing/shared';
import { sendSmtpMail } from '../services/emailService.js';

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

export async function ticketRoutes(fastify: FastifyInstance) {
  fastify.get('/', { preHandler: authenticate }, async (req, reply) => {
    const q = req.query as Record<string, string>;
    const companyId =
      req.user!.role === 'company_user' ? (req.user!.company_id ?? undefined) : undefined;
    if (req.user!.role === 'company_user' && !companyId) {
      return reply.status(403).send({ error: 'Invalid company user' });
    }

    const tickets = await ticketService.list({
      status:     q.status     as 'open' | 'closed' | undefined,
      priority:   q.priority   as 'low' | 'medium' | 'high' | undefined,
      channelId:  q.channel_id,
      assignedTo: q.assigned_to,
      from:       q.from,
      to:         q.to,
      companyId,
      limit:      q.limit  ? Number(q.limit)  : 50,
      offset:     q.offset ? Number(q.offset) : 0,
    });
    return reply.send(tickets);
  });

  fastify.get('/:id', { preHandler: authenticate }, async (req, reply) => {
    const { id } = req.params as { id: string };
    if (!(await userCanAccessTicket(req.user!, id))) {
      return reply.status(403).send({ error: 'Forbidden' });
    }
    const ticket = await ticketService.getById(id);
    if (!ticket) return reply.status(404).send({ error: 'Ticket not found' });
    const messages = await messagingService.getMessages(id);
    return reply.send({ ...ticket, messages });
  });

  fastify.patch('/:id', { preHandler: staffOnly }, async (req, reply) => {
    const { id } = req.params as { id: string };
    if (!(await userCanAccessTicket(req.user!, id))) {
      return reply.status(403).send({ error: 'Forbidden' });
    }
    const body = req.body as {
      status?: 'closed';
      priority?: 'low' | 'medium' | 'high';
      due_date?: string | null;
      assigned_to?: string | null;
      needs_escalation?: boolean;
    };
    const actorId = req.user!.sub;

    let ticket;
    if (body.status === 'closed')            ticket = await ticketService.closeTicket(id, actorId, fastify.io);
    if (body.priority !== undefined)         ticket = await ticketService.setPriority(id, body.priority, actorId);
    if ('due_date' in body)                  ticket = await ticketService.setDueDate(id, body.due_date ?? null, actorId);
    if ('assigned_to' in body)               ticket = await ticketService.assignTo(id, body.assigned_to ?? null, actorId);
    if (body.needs_escalation !== undefined)
      ticket = await ticketService.setNeedsEscalation(id, body.needs_escalation, actorId);

    ticket = ticket ?? await ticketService.getById(id);
    if (!ticket) return reply.status(404).send({ error: 'Ticket not found' });

    fastify.io.to(`ticket:${id}`).emit('ticket:updated', { ticket });
    return reply.send(ticket);
  });

  fastify.post('/:id/messages', { preHandler: staffOnly }, async (req, reply) => {
    const { id } = req.params as { id: string };
    const { content } = req.body as { content: string };
    const actorId = req.user!.sub;

    if (!content?.trim()) return reply.status(400).send({ error: 'content is required' });

    if (!(await userCanAccessTicket(req.user!, id))) {
      return reply.status(403).send({ error: 'Forbidden' });
    }

    const ticket = await ticketService.getById(id);
    if (!ticket) return reply.status(404).send({ error: 'Ticket not found' });
    if (ticket.status === 'closed') return reply.status(400).send({ error: 'Ticket is closed' });

    const channel = await channelService.getById(ticket.channel_id);

    if (channel && isPortalChannelPhone(channel.phone_number)) {
      const message = await messagingService.appendMessage(
        id,
        'outbound',
        content.trim(),
        null,
        actorId,
        'portal',
      );
      await auditService.log(actorId, 'user', 'message.sent', 'message', message.id, {
        channelId: ticket.channel_id,
        ticketId: id,
        source: 'portal',
      });
      fastify.io.to(`ticket:${id}`).emit('message:sent', { messageId: message.id, status: 'ok' });
      fastify.io.to(`ticket:${id}`).emit('message:new', { ticketId: id, message });
      return reply.status(201).send(message);
    }

    if (channel?.kind === 'email') {
      const [contact] = await sql<[{ phone_number: string }]>`
        SELECT phone_number FROM contacts WHERE id = ${ticket.contact_id}
      `;
      if (!contact?.phone_number.startsWith('email:') || !contact.phone_number.includes('@')) {
        return reply.status(400).send({ error: 'Invalid email contact' });
      }
      const to = contact.phone_number.slice(6);
      const sent = await sendSmtpMail({
        to,
        subject: `Support: ticket ${id.slice(0, 8)}`,
        text: content.trim(),
      });
      if (!sent.ok) {
        return reply.status(503).send({ error: sent.error ?? 'Email send failed (configure SMTP on API)' });
      }
      const message = await messagingService.appendMessage(id, 'outbound', content.trim(), null, actorId, 'email');
      await auditService.log(actorId, 'user', 'message.sent', 'message', message.id, {
        channelId: ticket.channel_id,
        ticketId: id,
        source: 'email',
      });
      fastify.io.to(`ticket:${id}`).emit('message:sent', { messageId: message.id, status: 'ok' });
      fastify.io.to(`ticket:${id}`).emit('message:new', { ticketId: id, message });
      return reply.status(201).send(message);
    }

    const message = await messagingService.appendMessage(id, 'outbound', content, null, actorId);

    try {
      if (!channel || channel.status !== 'connected') {
        throw new Error('Channel not connected');
      }

      const [contact] = await sql<[{ phone_number: string }]>`
        SELECT phone_number FROM contacts WHERE id = ${ticket.contact_id}
      `;
      if (!contact) throw new Error('Contact not found');

      const waMessageId = await channelService.sendMessage(
        ticket.channel_id,
        contact.phone_number,
        content.trim(),
      );

      if (waMessageId) {
        await sql`UPDATE messages SET wa_message_id = ${waMessageId} WHERE id = ${message.id}`;
        message.wa_message_id = waMessageId;
      }

      await auditService.log(actorId, 'user', 'message.sent', 'message', message.id, {
        channelId: ticket.channel_id,
        ticketId: id,
      });

      fastify.io.to(`ticket:${id}`).emit('message:sent', { messageId: message.id, status: 'ok' });
      fastify.io.to(`ticket:${id}`).emit('message:new', { ticketId: id, message });
    } catch (err) {
      await auditService.log(actorId, 'user', 'message.send_failed', 'message', message.id, {
        error: String(err),
      });
      fastify.io.to(`ticket:${id}`).emit('message:sent', { messageId: message.id, status: 'error', error: String(err) });
    }

    return reply.status(201).send(message);
  });
}
