import {
  and,
  createDb,
  desc,
  eq,
  sql,
  supportMessages,
  supportTickets,
  users,
} from '@bmm/db';
import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { audit } from '../lib/audit.js';
import { checkDailyLimit } from '../lib/rate-limit.js';
import { requireAdmin, requireAuth } from '../plugins/session.js';

const db = createDb();

const NewTicketBody = z.object({
  subject: z.string().min(3).max(200),
  body: z.string().min(10).max(10_000),
});

const GuestTicketBody = z.object({
  email: z.string().email(),
  subject: z.string().min(3).max(200),
  body: z.string().min(10).max(10_000),
});

const NewMessageBody = z.object({
  body: z.string().min(1).max(10_000),
});

const StatusBody = z.object({
  status: z.enum(['awaiting_admin', 'awaiting_user', 'closed']),
});

export async function supportRoutes(app: FastifyInstance): Promise<void> {
  // ─── User-side ──────────────────────────────────────────────────────────
  app.post('/v1/support/tickets', { preHandler: requireAuth }, async (req, reply) => {
    const user = req.user!;
    const parsed = NewTicketBody.safeParse(req.body);
    if (!parsed.success) return reply.code(400).send({ error: 'invalid_input' });

    const [ticket] = await db
      .insert(supportTickets)
      .values({
        userId: user.userId,
        orgId: user.orgId,
        subject: parsed.data.subject,
        status: 'awaiting_admin',
      })
      .returning();
    if (!ticket) return reply.code(500).send({ error: 'ticket_create_failed' });

    await db.insert(supportMessages).values({
      ticketId: ticket.id,
      authorUserId: user.userId,
      authorIsAdmin: false,
      body: parsed.data.body,
    });

    await audit({
      orgId: user.orgId,
      userId: user.userId,
      action: 'support.ticket_created',
      resourceType: 'support_ticket',
      resourceId: ticket.id,
      metadata: { subject: parsed.data.subject },
      ipAddress: req.ip,
    });

    return reply.send({ ticket });
  });

  app.get('/v1/support/tickets', { preHandler: requireAuth }, async (req, reply) => {
    const user = req.user!;
    const rows = await db
      .select()
      .from(supportTickets)
      .where(eq(supportTickets.userId, user.userId))
      .orderBy(desc(supportTickets.lastMessageAt));
    return reply.send({ tickets: rows });
  });

  app.get('/v1/support/tickets/:id', { preHandler: requireAuth }, async (req, reply) => {
    const user = req.user!;
    const Params = z.object({ id: z.string().uuid() });
    const parsed = Params.safeParse(req.params);
    if (!parsed.success) return reply.code(400).send({ error: 'invalid_id' });

    const [ticket] = await db
      .select()
      .from(supportTickets)
      .where(
        and(eq(supportTickets.id, parsed.data.id), eq(supportTickets.userId, user.userId)),
      )
      .limit(1);
    if (!ticket) return reply.code(404).send({ error: 'not_found' });

    const messages = await db
      .select()
      .from(supportMessages)
      .where(eq(supportMessages.ticketId, ticket.id))
      .orderBy(supportMessages.createdAt);

    return reply.send({ ticket, messages });
  });

  app.post(
    '/v1/support/tickets/:id/messages',
    { preHandler: requireAuth },
    async (req, reply) => {
      const user = req.user!;
      const Params = z.object({ id: z.string().uuid() });
      const parsed = Params.safeParse(req.params);
      if (!parsed.success) return reply.code(400).send({ error: 'invalid_id' });
      const body = NewMessageBody.safeParse(req.body);
      if (!body.success) return reply.code(400).send({ error: 'invalid_input' });

      const [ticket] = await db
        .select()
        .from(supportTickets)
        .where(
          and(eq(supportTickets.id, parsed.data.id), eq(supportTickets.userId, user.userId)),
        )
        .limit(1);
      if (!ticket) return reply.code(404).send({ error: 'not_found' });

      await db.insert(supportMessages).values({
        ticketId: ticket.id,
        authorUserId: user.userId,
        authorIsAdmin: false,
        body: body.data.body,
      });

      await db
        .update(supportTickets)
        .set({
          status: 'awaiting_admin',
          lastMessageAt: new Date(),
          updatedAt: new Date(),
        })
        .where(eq(supportTickets.id, ticket.id));

      return reply.send({ ok: true });
    },
  );

  // ─── Public contact form (no auth) ─────────────────────────────────────
  // Satisfies UWG Art. 3 lit. s ("easy electronic contact") for non-logged-in
  // visitors. Rate-limited per IP to prevent spam-flood of admin queue.
  app.post('/v1/contact', async (req, reply) => {
    const parsed = GuestTicketBody.safeParse(req.body);
    if (!parsed.success) return reply.code(400).send({ error: 'invalid_input' });

    const rl = await checkDailyLimit('contact', req.ip, 3);
    if (!rl.ok) {
      return reply.code(429).send({
        error: 'rate_limited',
        detail: 'Too many contact submissions from this IP. Try again tomorrow.',
      });
    }

    const [ticket] = await db
      .insert(supportTickets)
      .values({
        guestEmail: parsed.data.email,
        subject: parsed.data.subject,
        status: 'awaiting_admin',
      })
      .returning();
    if (!ticket) return reply.code(500).send({ error: 'ticket_create_failed' });

    await db.insert(supportMessages).values({
      ticketId: ticket.id,
      authorUserId: null,
      authorIsAdmin: false,
      body: parsed.data.body,
    });

    return reply.send({ ok: true });
  });

  // ─── Admin-side ────────────────────────────────────────────────────────
  app.get(
    '/v1/admin/support/counts',
    { preHandler: requireAdmin },
    async (_req, reply) => {
      const [row] = await db
        .select({ count: sql<number>`count(*)::int` })
        .from(supportTickets)
        .where(eq(supportTickets.status, 'awaiting_admin'));
      return reply.send({ awaitingAdmin: row?.count ?? 0 });
    },
  );

  app.get(
    '/v1/admin/support/tickets',
    { preHandler: requireAdmin },
    async (_req, reply) => {
      const rows = await db
        .select({
          ticket: supportTickets,
          userEmail: users.email,
          userName: users.name,
        })
        .from(supportTickets)
        .leftJoin(users, eq(users.id, supportTickets.userId))
        .orderBy(desc(supportTickets.lastMessageAt))
        .limit(200);
      return reply.send({ tickets: rows });
    },
  );

  app.get(
    '/v1/admin/support/tickets/:id',
    { preHandler: requireAdmin },
    async (req, reply) => {
      const Params = z.object({ id: z.string().uuid() });
      const parsed = Params.safeParse(req.params);
      if (!parsed.success) return reply.code(400).send({ error: 'invalid_id' });

      const [row] = await db
        .select({ ticket: supportTickets, userEmail: users.email, userName: users.name })
        .from(supportTickets)
        .leftJoin(users, eq(users.id, supportTickets.userId))
        .where(eq(supportTickets.id, parsed.data.id))
        .limit(1);
      if (!row) return reply.code(404).send({ error: 'not_found' });

      const messages = await db
        .select()
        .from(supportMessages)
        .where(eq(supportMessages.ticketId, parsed.data.id))
        .orderBy(supportMessages.createdAt);

      return reply.send({ ticket: row.ticket, userEmail: row.userEmail, userName: row.userName, messages });
    },
  );

  app.post(
    '/v1/admin/support/tickets/:id/messages',
    { preHandler: requireAdmin },
    async (req, reply) => {
      const user = req.user!;
      const Params = z.object({ id: z.string().uuid() });
      const parsed = Params.safeParse(req.params);
      if (!parsed.success) return reply.code(400).send({ error: 'invalid_id' });
      const body = NewMessageBody.safeParse(req.body);
      if (!body.success) return reply.code(400).send({ error: 'invalid_input' });

      await db.insert(supportMessages).values({
        ticketId: parsed.data.id,
        authorUserId: user.userId,
        authorIsAdmin: true,
        body: body.data.body,
      });

      await db
        .update(supportTickets)
        .set({
          status: 'awaiting_user',
          lastMessageAt: new Date(),
          updatedAt: new Date(),
        })
        .where(eq(supportTickets.id, parsed.data.id));

      await audit({
        orgId: user.orgId,
        userId: user.userId,
        action: 'support.admin_reply',
        resourceType: 'support_ticket',
        resourceId: parsed.data.id,
      });

      return reply.send({ ok: true });
    },
  );

  app.post(
    '/v1/admin/support/tickets/:id/status',
    { preHandler: requireAdmin },
    async (req, reply) => {
      const Params = z.object({ id: z.string().uuid() });
      const parsed = Params.safeParse(req.params);
      if (!parsed.success) return reply.code(400).send({ error: 'invalid_id' });
      const body = StatusBody.safeParse(req.body);
      if (!body.success) return reply.code(400).send({ error: 'invalid_input' });

      await db
        .update(supportTickets)
        .set({
          status: body.data.status,
          closedAt: body.data.status === 'closed' ? new Date() : null,
          updatedAt: new Date(),
        })
        .where(eq(supportTickets.id, parsed.data.id));

      return reply.send({ ok: true });
    },
  );
}
