/**
 * Invitation code REST endpoints.
 *
 * GET    /invitations           — list all codes
 * GET    /invitations/:id       — get by ID
 * POST   /invitations           — create a new code
 * PUT    /invitations/:id       — update a code
 * DELETE /invitations/:id       — delete a code
 * POST   /invitations/redeem    — redeem a code
 * GET    /invitations/count     — count codes
 * POST   /invitations/bulk      — bulk create codes from array
 */

import type { FastifyInstance } from 'fastify';
import { getRequestProductId } from '../../lib/request-context.js';
import { BadRequestError, NotFoundError } from '../../lib/errors.js';
import { dispatchInvitationRedeemed } from '../../lib/webhooks.js';
import * as repo from './repository.js';
import {
  CreateInvitationSchema,
  UpdateInvitationSchema,
  RedeemInvitationSchema,
  type InvitationCodeDoc,
} from './types.js';

export async function invitationRoutes(app: FastifyInstance) {
  // List
  app.get('/invitations', async req => {
    const { limit = '100', offset = '0' } = req.query as Record<string, string>;
    const productId = getRequestProductId(req);
    const items = await repo.list(Number(limit), Number(offset), productId);
    return { invitations: items, count: items.length };
  });

  // Count
  app.get('/invitations/count', async req => {
    const productId = getRequestProductId(req);
    const total = await repo.count(productId);
    return { count: total };
  });

  // Get by ID
  app.get('/invitations/:id', async req => {
    const { id } = req.params as { id: string };
    const doc = await repo.getById(id);
    if (!doc) throw new NotFoundError('Invitation code not found');
    return doc;
  });

  // Create
  app.post('/invitations', async (req, reply) => {
    const parsed = CreateInvitationSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const input = parsed.data;
    const now = new Date().toISOString();
    const productId = getRequestProductId(req);
    const doc: InvitationCodeDoc = {
      id: `inv_${crypto.randomUUID()}`,
      productId,
      code: input.code.toUpperCase().replace(/[^A-Z0-9-]/g, ''),
      description: input.description,
      createdBy: input.createdBy,
      grantPlan: input.grantPlan,
      grantTrialDays: input.grantTrialDays,
      bonusTokens: input.bonusTokens,
      maxUses: input.maxUses,
      currentUses: 0,
      redeemedBy: [],
      status: 'active',
      expiresAt: input.expiresAt,
      createdAt: now,
      updatedAt: now,
    };
    const created = await repo.create(doc);
    reply.code(201);
    return created;
  });

  // Update
  app.put('/invitations/:id', async req => {
    const { id } = req.params as { id: string };
    const parsed = UpdateInvitationSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const updated = await repo.update(id, parsed.data);
    if (!updated) throw new NotFoundError('Invitation code not found');
    return updated;
  });

  // Delete
  app.delete('/invitations/:id', async (req, reply) => {
    const { id } = req.params as { id: string };
    const ok = await repo.remove(id);
    if (!ok) throw new NotFoundError('Invitation code not found');
    reply.code(204);
    return;
  });

  // Bulk create — accepts a JSON array of invitation inputs
  app.post('/invitations/bulk', async (req, reply) => {
    const body = req.body;
    if (!Array.isArray(body)) {
      throw new BadRequestError('Request body must be a JSON array of invitation objects');
    }
    if (body.length === 0) {
      throw new BadRequestError('Array must not be empty');
    }
    if (body.length > 500) {
      throw new BadRequestError('Maximum 500 invitations per bulk request');
    }

    const productId = getRequestProductId(req);
    const results: { created: InvitationCodeDoc[]; errors: { index: number; error: string }[] } = {
      created: [],
      errors: [],
    };

    for (let i = 0; i < body.length; i++) {
      const parsed = CreateInvitationSchema.safeParse(body[i]);
      if (!parsed.success) {
        results.errors.push({
          index: i,
          error: parsed.error.issues.map(e => e.message).join('; '),
        });
        continue;
      }
      const input = parsed.data;
      const now = new Date().toISOString();
      const doc: InvitationCodeDoc = {
        id: `inv_${crypto.randomUUID()}`,
        productId,
        code: input.code.toUpperCase().replace(/[^A-Z0-9-]/g, ''),
        description: input.description,
        createdBy: input.createdBy,
        grantPlan: input.grantPlan,
        grantTrialDays: input.grantTrialDays,
        bonusTokens: input.bonusTokens,
        maxUses: input.maxUses,
        currentUses: 0,
        redeemedBy: [],
        status: 'active',
        expiresAt: input.expiresAt,
        createdAt: now,
        updatedAt: now,
      };
      try {
        const created = await repo.create(doc);
        results.created.push(created);
      } catch (err) {
        results.errors.push({
          index: i,
          error: err instanceof Error ? err.message : 'Creation failed',
        });
      }
    }

    reply.code(results.errors.length === 0 ? 201 : 207);
    return {
      total: body.length,
      created: results.created.length,
      failed: results.errors.length,
      invitations: results.created,
      errors: results.errors,
    };
  });

  // Redeem
  app.post('/invitations/redeem', async req => {
    const parsed = RedeemInvitationSchema.safeParse(req.body);
    if (!parsed.success) {
      throw new BadRequestError(parsed.error.issues.map(i => i.message).join('; '));
    }
    const redeemed = await repo.redeem(parsed.data.code, parsed.data.userId);
    if (!redeemed) {
      throw new BadRequestError(
        'Code is invalid, expired, fully redeemed, or already used by this user'
      );
    }

    // Fire-and-forget webhook callback
    dispatchInvitationRedeemed({
      code: redeemed.code,
      userId: parsed.data.userId,
      grantPlan: redeemed.grantPlan,
      grantTrialDays: redeemed.grantTrialDays,
      bonusTokens: redeemed.bonusTokens,
      invitationId: redeemed.id,
    });

    return redeemed;
  });
}
