/**
 * WebhookController — request handlers for webhook subscription management and delivery history.
 *
 * Handlers:
 *   POST   /webhooks              → createSubscription
 *   GET    /webhooks              → listSubscriptions
 *   GET    /webhooks/:id          → getSubscription
 *   PATCH  /webhooks/:id          → updateSubscription
 *   DELETE /webhooks/:id          → deleteSubscription
 *   GET    /webhooks/:id/deliveries → listDeliveries
 *
 * All handlers read `req.user.organization_id` to scope operations to the
 * authenticated agent's organization.
 */

import { Request, Response, NextFunction } from 'express';
import { WebhookService } from '../services/WebhookService.js';
import { ITokenPayload } from '../types/index.js';
import {
  ICreateWebhookRequest,
  IUpdateWebhookRequest,
} from '../types/webhook.js';
import { AuthenticationError } from '../utils/errors.js';

/**
 * Controller for all webhook subscription and delivery endpoints.
 * Delegates all business logic to WebhookService.
 */
export class WebhookController {
  /**
   * @param webhookService - Service that manages webhook subscriptions and deliveries.
   */
  constructor(private readonly webhookService: WebhookService) {}

  // ─────────────────────────────────────────────────────────────────────────
  // Private helpers
  // ─────────────────────────────────────────────────────────────────────────

  /**
   * Extracts and asserts the organization_id from the token payload.
   * Throws AuthenticationError (401) if the token was issued without an org scope.
   */
  private requireOrgId(user: ITokenPayload): string {
    if (!user.organization_id) {
      throw new AuthenticationError('Token does not carry an organization_id — multi-tenant context required');
    }
    return user.organization_id;
  }

  // ─────────────────────────────────────────────────────────────────────────
  // Public handlers
  // ─────────────────────────────────────────────────────────────────────────

  /**
   * Creates a new webhook subscription for the authenticated agent's organization.
   *
   * Returns 201 with the subscription and the one-time signing secret.
   *
   * @param req - Express request. Body must conform to ICreateWebhookRequest.
   * @param res - Express response.
   * @param next - Express next function — forwards errors to the global error handler.
   */
  async createSubscription(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const user = req.user as ITokenPayload;
      const body = req.body as ICreateWebhookRequest;
      const result = await this.webhookService.createSubscription(this.requireOrgId(user), body);
      res.status(201).json(result);
    } catch (err) {
      next(err);
    }
  }

  /**
   * Returns all webhook subscriptions for the authenticated agent's organization.
   *
   * Responds 200 with an array of IWebhookSubscription objects.
   *
   * @param req - Express request.
   * @param res - Express response.
   * @param next - Express next function — forwards errors to the global error handler.
   */
  async listSubscriptions(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const user = req.user as ITokenPayload;
      const subscriptions = await this.webhookService.listSubscriptions(this.requireOrgId(user));
      res.status(200).json(subscriptions);
    } catch (err) {
      next(err);
    }
  }

  /**
   * Returns a single webhook subscription by its UUID.
   *
   * Responds 200 with the matching IWebhookSubscription.
   * Responds 404 if the subscription does not exist or belongs to another organization.
   *
   * @param req - Express request. `req.params.id` must be the subscription UUID.
   * @param res - Express response.
   * @param next - Express next function — forwards errors to the global error handler.
   */
  async getSubscription(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const user = req.user as ITokenPayload;
      const { id } = req.params;
      const subscription = await this.webhookService.getSubscription(id, this.requireOrgId(user));
      res.status(200).json(subscription);
    } catch (err) {
      next(err);
    }
  }

  /**
   * Partially updates an existing webhook subscription.
   *
   * Responds 200 with the updated IWebhookSubscription.
   * Responds 404 if the subscription does not exist or belongs to another organization.
   *
   * @param req - Express request. `req.params.id` is the subscription UUID; body is IUpdateWebhookRequest.
   * @param res - Express response.
   * @param next - Express next function — forwards errors to the global error handler.
   */
  async updateSubscription(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const user = req.user as ITokenPayload;
      const { id } = req.params;
      const body = req.body as IUpdateWebhookRequest;
      const subscription = await this.webhookService.updateSubscription(id, this.requireOrgId(user), body);
      res.status(200).json(subscription);
    } catch (err) {
      next(err);
    }
  }

  /**
   * Permanently deletes a webhook subscription and all its delivery records.
   *
   * Responds 204 No Content on success.
   * Responds 404 if the subscription does not exist or belongs to another organization.
   *
   * @param req - Express request. `req.params.id` must be the subscription UUID.
   * @param res - Express response.
   * @param next - Express next function — forwards errors to the global error handler.
   */
  async deleteSubscription(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const user = req.user as ITokenPayload;
      const { id } = req.params;
      await this.webhookService.deleteSubscription(id, this.requireOrgId(user));
      res.status(204).send();
    } catch (err) {
      next(err);
    }
  }

  /**
   * Returns a paginated list of delivery records for a webhook subscription.
   *
   * Responds 200 with an IPaginatedDeliveriesResponse.
   * Responds 404 if the subscription does not exist or belongs to another organization.
   *
   * @param req - Express request. `req.params.id` is the subscription UUID.
   *              Optional query params: limit (default 20), offset (default 0).
   * @param res - Express response.
   * @param next - Express next function — forwards errors to the global error handler.
   */
  async listDeliveries(req: Request, res: Response, next: NextFunction): Promise<void> {
    try {
      const user = req.user as ITokenPayload;
      const { id } = req.params;
      const limit = Math.max(1, parseInt(String(req.query['limit'] ?? '20'), 10) || 20);
      const offset = Math.max(0, parseInt(String(req.query['offset'] ?? '0'), 10) || 0);
      const result = await this.webhookService.listDeliveries(id, this.requireOrgId(user), limit, offset);
      res.status(200).json(result);
    } catch (err) {
      next(err);
    }
  }
}
