import { Injectable } from '@nestjs/common';
import { AppLoggerService } from 'src/common/services/logger.service';
import {
  ZOOM_ANALYTICS_API_PATHS,
  ZOOM_ANALYTICS_HTTP_STATUS,
  ZOOM_ANALYTICS_API_CODE,
  ZOOM_ANALYTICS_LOG,
} from '../constants/zoom-analytics.constants';
import { ZoomAnalyticsHttpUtil } from './zoom-analytics-http.util';

export interface ZoomAnalyticsSessionReport {
  start_time?: string;
  duration?: number;
  end_time?: string;
  [key: string]: unknown;
}

/**
 * New, minimal client for `GET /report/webinars/{id}` and
 * `GET /report/meetings/{id}` — the Zoom endpoints needed for actual start
 * time + duration that don't exist on the existing ZoomApiService (and are
 * not added there, per the no-touch rule). Requires `report:read:meeting:admin`
 * / `report:read:webinar:admin` granular scopes — confirmed missing on this
 * account's Server-to-Server app as of 2026-07-13 (Zoom returns HTTP 400,
 * code 4711). A scope-missing rejection degrades to `null` (logged once)
 * exactly like "report not generated yet", so reconcile still completes with
 * live-event-log data instead of failing the whole mark-complete flow.
 */
@Injectable()
export class ZoomReportApiClient {
  private unavailableWarned = false;

  constructor(
    private readonly http: ZoomAnalyticsHttpUtil,
    private readonly logger: AppLoggerService,
  ) {}

  /**
   * Returns the webinar report, or `null` if it isn't generated yet (Zoom
   * code 3001 / 404) — not an error, the caller should retry later.
   */
  async fetchWebinarReport(webinarIdExt: string): Promise<ZoomAnalyticsSessionReport | null> {
    return this.fetchReport(ZOOM_ANALYTICS_API_PATHS.REPORT_WEBINAR(webinarIdExt));
  }

  /**
   * Returns the meeting report, or `null` if it isn't generated yet (Zoom
   * code 3001 / 404) — not an error, the caller should retry later.
   */
  async fetchMeetingReport(meetingIdExt: string): Promise<ZoomAnalyticsSessionReport | null> {
    return this.fetchReport(ZOOM_ANALYTICS_API_PATHS.REPORT_MEETING(meetingIdExt));
  }

  private async fetchReport(path: string): Promise<ZoomAnalyticsSessionReport | null> {
    try {
      return await this.http.get<ZoomAnalyticsSessionReport>(path);
    } catch (error: any) {
      const status = error?.response?.status;
      const code = error?.response?.data?.code;
      if (status === ZOOM_ANALYTICS_HTTP_STATUS.NOT_FOUND || code === ZOOM_ANALYTICS_API_CODE.PAST_DATA_NOT_READY) {
        return null;
      }
      // Any 400/401/403 means Zoom is refusing this read (missing report scopes arrive as
      // 400 + code 4711, but Zoom's 400 codes vary) — it must not fail reconcile. The
      // report only contributes the official start/duration; everything else comes from
      // local data.
      const isUnavailable =
        status === ZOOM_ANALYTICS_HTTP_STATUS.BAD_REQUEST ||
        status === ZOOM_ANALYTICS_HTTP_STATUS.UNAUTHORIZED ||
        status === ZOOM_ANALYTICS_HTTP_STATUS.FORBIDDEN;
      if (isUnavailable) {
        if (!this.unavailableWarned) {
          this.logger.warn(ZOOM_ANALYTICS_LOG.REPORT_UNAVAILABLE, { path, status, code });
          this.unavailableWarned = true;
        }
        return null;
      }
      throw error;
    }
  }
}
